1#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6 issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
9#![cfg_attr(not(test), warn(unused_crate_dependencies))]
10
11use std::{
12 fmt::Debug,
13 future::{poll_fn, Future},
14 sync::Arc,
15 task::Poll,
16};
17
18use alloy_eips::BlockNumHash;
19use futures_util::FutureExt;
20use reth_chainspec::{ChainSpec, MAINNET};
21use reth_consensus::test_utils::TestConsensus;
22use reth_db::{
23 test_utils::{create_test_rw_db, create_test_static_files_dir, TempDatabase},
24 DatabaseEnv,
25};
26use reth_db_common::init::init_genesis;
27use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
28use reth_evm_ethereum::MockEvmConfig;
29use reth_execution_types::Chain;
30use reth_exex::{ExExContext, ExExEvent, ExExNotification, ExExNotifications, Wal};
31use reth_network::{config::rng_secret_key, NetworkConfigBuilder, NetworkManager};
32use reth_node_api::{
33 FullNodeTypes, FullNodeTypesAdapter, NodePrimitives, NodeTypes, NodeTypesWithDBAdapter,
34};
35use reth_node_builder::{
36 components::{
37 BasicPayloadServiceBuilder, Components, ComponentsBuilder, ConsensusBuilder,
38 ExecutorBuilder, PoolBuilder,
39 },
40 BuilderContext, Node, NodeAdapter, RethFullAdapter,
41};
42use reth_node_core::node_config::NodeConfig;
43use reth_node_ethereum::{
44 node::{
45 EthereumAddOns, EthereumEngineValidatorBuilder, EthereumEthApiBuilder,
46 EthereumNetworkBuilder, EthereumPayloadBuilder,
47 },
48 EthEngineTypes,
49};
50use reth_payload_builder::noop::NoopPayloadBuilderService;
51use reth_primitives_traits::{Block as _, RecoveredBlock};
52use reth_provider::{
53 providers::{BlockchainProvider, StaticFileProvider},
54 BlockReader, EthStorage, ProviderFactory,
55};
56use reth_tasks::TaskManager;
57use reth_transaction_pool::test_utils::{testing_pool, TestPool};
58use tempfile::TempDir;
59use thiserror::Error;
60use tokio::sync::mpsc::{Sender, UnboundedReceiver};
61
62#[derive(Debug, Default, Clone, Copy)]
64#[non_exhaustive]
65pub struct TestPoolBuilder;
66
67impl<Node> PoolBuilder<Node> for TestPoolBuilder
68where
69 Node: FullNodeTypes<Types: NodeTypes<Primitives: NodePrimitives<SignedTx = TransactionSigned>>>,
70{
71 type Pool = TestPool;
72
73 async fn build_pool(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
74 Ok(testing_pool())
75 }
76}
77
78#[derive(Debug, Default, Clone, Copy)]
80#[non_exhaustive]
81pub struct TestExecutorBuilder;
82
83impl<Node> ExecutorBuilder<Node> for TestExecutorBuilder
84where
85 Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>,
86{
87 type EVM = MockEvmConfig;
88
89 async fn build_evm(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
90 let evm_config = MockEvmConfig::default();
91 Ok(evm_config)
92 }
93}
94
95#[derive(Debug, Default, Clone, Copy)]
97#[non_exhaustive]
98pub struct TestConsensusBuilder;
99
100impl<Node> ConsensusBuilder<Node> for TestConsensusBuilder
101where
102 Node: FullNodeTypes,
103{
104 type Consensus = Arc<TestConsensus>;
105
106 async fn build_consensus(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
107 Ok(Arc::new(TestConsensus::default()))
108 }
109}
110
111#[derive(Debug, Default, Clone, Copy)]
113#[non_exhaustive]
114pub struct TestNode;
115
116impl NodeTypes for TestNode {
117 type Primitives = EthPrimitives;
118 type ChainSpec = ChainSpec;
119 type Storage = EthStorage;
120 type Payload = EthEngineTypes;
121}
122
123impl<N> Node<N> for TestNode
124where
125 N: FullNodeTypes<Types = Self>,
126{
127 type ComponentsBuilder = ComponentsBuilder<
128 N,
129 TestPoolBuilder,
130 BasicPayloadServiceBuilder<EthereumPayloadBuilder>,
131 EthereumNetworkBuilder,
132 TestExecutorBuilder,
133 TestConsensusBuilder,
134 >;
135 type AddOns =
136 EthereumAddOns<NodeAdapter<N>, EthereumEthApiBuilder, EthereumEngineValidatorBuilder>;
137
138 fn components_builder(&self) -> Self::ComponentsBuilder {
139 ComponentsBuilder::default()
140 .node_types::<N>()
141 .pool(TestPoolBuilder::default())
142 .executor(TestExecutorBuilder::default())
143 .payload(BasicPayloadServiceBuilder::default())
144 .network(EthereumNetworkBuilder::default())
145 .consensus(TestConsensusBuilder::default())
146 }
147
148 fn add_ons(&self) -> Self::AddOns {
149 EthereumAddOns::default()
150 }
151}
152
153pub type TmpDB = Arc<TempDatabase<DatabaseEnv>>;
155pub type Adapter = NodeAdapter<RethFullAdapter<TmpDB, TestNode>>;
158pub type TestExExContext = ExExContext<Adapter>;
160
161#[derive(Debug)]
163pub struct TestExExHandle {
164 pub genesis: RecoveredBlock<reth_ethereum_primitives::Block>,
166 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
168 pub events_rx: UnboundedReceiver<ExExEvent>,
170 pub notifications_tx: Sender<ExExNotification>,
172 pub tasks: TaskManager,
174 _wal_directory: TempDir,
176}
177
178impl TestExExHandle {
179 pub async fn send_notification_chain_committed(&self, chain: Chain) -> eyre::Result<()> {
181 self.notifications_tx
182 .send(ExExNotification::ChainCommitted { new: Arc::new(chain) })
183 .await?;
184 Ok(())
185 }
186
187 pub async fn send_notification_chain_reorged(
189 &self,
190 old: Chain,
191 new: Chain,
192 ) -> eyre::Result<()> {
193 self.notifications_tx
194 .send(ExExNotification::ChainReorged { old: Arc::new(old), new: Arc::new(new) })
195 .await?;
196 Ok(())
197 }
198
199 pub async fn send_notification_chain_reverted(&self, chain: Chain) -> eyre::Result<()> {
201 self.notifications_tx
202 .send(ExExNotification::ChainReverted { old: Arc::new(chain) })
203 .await?;
204 Ok(())
205 }
206
207 #[track_caller]
209 pub fn assert_events_empty(&self) {
210 assert!(self.events_rx.is_empty());
211 }
212
213 #[track_caller]
216 pub fn assert_event_finished_height(&mut self, height: BlockNumHash) -> eyre::Result<()> {
217 let event = self.events_rx.try_recv()?;
218 assert_eq!(event, ExExEvent::FinishedHeight(height));
219 Ok(())
220 }
221}
222
223pub async fn test_exex_context_with_chain_spec(
235 chain_spec: Arc<ChainSpec>,
236) -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
237 let transaction_pool = testing_pool();
238 let evm_config = MockEvmConfig::default();
239 let consensus = Arc::new(TestConsensus::default());
240
241 let (static_dir, _) = create_test_static_files_dir();
242 let db = create_test_rw_db();
243 let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<TestNode, _>>::new(
244 db,
245 chain_spec.clone(),
246 StaticFileProvider::read_write(static_dir.keep()).expect("static file provider"),
247 );
248
249 let genesis_hash = init_genesis(&provider_factory)?;
250 let provider = BlockchainProvider::new(provider_factory.clone())?;
251
252 let network_manager = NetworkManager::new(
253 NetworkConfigBuilder::new(rng_secret_key())
254 .with_unused_discovery_port()
255 .with_unused_listener_port()
256 .build(provider_factory.clone()),
257 )
258 .await?;
259 let network = network_manager.handle().clone();
260 let tasks = TaskManager::current();
261 let task_executor = tasks.executor();
262 tasks.executor().spawn(network_manager);
263
264 let (_, payload_builder_handle) = NoopPayloadBuilderService::<EthEngineTypes>::new();
265
266 let components = NodeAdapter::<FullNodeTypesAdapter<_, _, _>, _> {
267 components: Components {
268 transaction_pool,
269 evm_config,
270 consensus,
271 network,
272 payload_builder_handle,
273 },
274 task_executor,
275 provider,
276 };
277
278 let genesis = provider_factory
279 .block_by_hash(genesis_hash)?
280 .ok_or_else(|| eyre::eyre!("genesis block not found"))?
281 .seal_slow()
282 .try_recover()?;
283
284 let head = genesis.num_hash();
285
286 let wal_directory = tempfile::tempdir()?;
287 let wal = Wal::new(wal_directory.path())?;
288
289 let (events_tx, events_rx) = tokio::sync::mpsc::unbounded_channel();
290 let (notifications_tx, notifications_rx) = tokio::sync::mpsc::channel(1);
291 let notifications = ExExNotifications::new(
292 head,
293 components.provider.clone(),
294 components.components.evm_config.clone(),
295 notifications_rx,
296 wal.handle(),
297 );
298
299 let ctx = ExExContext {
300 head,
301 config: NodeConfig::test(),
302 reth_config: reth_config::Config::default(),
303 events: events_tx,
304 notifications,
305 components,
306 };
307
308 Ok((
309 ctx,
310 TestExExHandle {
311 genesis,
312 provider_factory,
313 events_rx,
314 notifications_tx,
315 tasks,
316 _wal_directory: wal_directory,
317 },
318 ))
319}
320
321pub async fn test_exex_context() -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
325 test_exex_context_with_chain_spec(MAINNET.clone()).await
326}
327
328pub trait PollOnce {
330 fn poll_once(&mut self) -> impl Future<Output = Result<(), PollOnceError>> + Send;
340}
341
342#[derive(Error, Debug)]
344pub enum PollOnceError {
345 #[error("Execution Extension future returned Ready, but it should never resolve")]
347 FutureIsReady,
348 #[error(transparent)]
350 FutureError(#[from] eyre::Error),
351}
352
353impl<F: Future<Output = eyre::Result<()>> + Unpin + Send> PollOnce for F {
354 async fn poll_once(&mut self) -> Result<(), PollOnceError> {
355 poll_fn(|cx| match self.poll_unpin(cx) {
356 Poll::Ready(Ok(())) => Poll::Ready(Err(PollOnceError::FutureIsReady)),
357 Poll::Ready(Err(err)) => Poll::Ready(Err(PollOnceError::FutureError(err))),
358 Poll::Pending => Poll::Ready(Ok(())),
359 })
360 .await
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[tokio::test]
369 async fn check_test_context_creation() {
370 let _ = test_exex_context().await.unwrap();
371 }
372}