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))]
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::{
24 create_test_rocksdb_dir, create_test_rw_db, create_test_static_files_dir, TempDatabase,
25 },
26 DatabaseEnv,
27};
28use reth_db_common::init::init_genesis;
29use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
30use reth_evm_ethereum::MockEvmConfig;
31use reth_execution_types::Chain;
32use reth_exex::{ExExContext, ExExEvent, ExExNotification, ExExNotifications, Wal};
33use reth_network::{config::rng_secret_key, NetworkConfigBuilder, NetworkManager};
34use reth_node_api::{
35 FullNodeTypes, FullNodeTypesAdapter, NodePrimitives, NodeTypes, NodeTypesWithDBAdapter,
36};
37use reth_node_builder::{
38 components::{
39 BasicPayloadServiceBuilder, Components, ComponentsBuilder, ConsensusBuilder,
40 ExecutorBuilder, PoolBuilder,
41 },
42 BuilderContext, Node, NodeAdapter, RethFullAdapter,
43};
44use reth_node_core::node_config::NodeConfig;
45use reth_node_ethereum::{
46 node::{
47 EthereumAddOns, EthereumEngineValidatorBuilder, EthereumEthApiBuilder,
48 EthereumNetworkBuilder, EthereumPayloadBuilder,
49 },
50 EthEngineTypes,
51};
52use reth_payload_builder::noop::NoopPayloadBuilderService;
53use reth_primitives_traits::{Block as _, RecoveredBlock};
54use reth_provider::{
55 providers::{BlockchainProvider, RocksDBProvider, StaticFileProvider},
56 BlockReader, EthStorage, ProviderFactory,
57};
58use reth_tasks::Runtime;
59use reth_transaction_pool::test_utils::{testing_pool, TestPool};
60use tempfile::TempDir;
61use thiserror::Error;
62use tokio::sync::mpsc::{Sender, UnboundedReceiver};
63
64#[derive(Debug, Default, Clone, Copy)]
66#[non_exhaustive]
67pub struct TestPoolBuilder;
68
69impl<Node, Evm: Send> PoolBuilder<Node, Evm> for TestPoolBuilder
70where
71 Node: FullNodeTypes<Types: NodeTypes<Primitives: NodePrimitives<SignedTx = TransactionSigned>>>,
72{
73 type Pool = TestPool;
74
75 async fn build_pool(
76 self,
77 _ctx: &BuilderContext<Node>,
78 _evm_config: Evm,
79 ) -> eyre::Result<Self::Pool> {
80 Ok(testing_pool())
81 }
82}
83
84#[derive(Debug, Default, Clone, Copy)]
86#[non_exhaustive]
87pub struct TestExecutorBuilder;
88
89impl<Node> ExecutorBuilder<Node> for TestExecutorBuilder
90where
91 Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>,
92{
93 type EVM = MockEvmConfig;
94
95 async fn build_evm(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
96 let evm_config = MockEvmConfig::default();
97 Ok(evm_config)
98 }
99}
100
101#[derive(Debug, Default, Clone, Copy)]
103#[non_exhaustive]
104pub struct TestConsensusBuilder;
105
106impl<Node> ConsensusBuilder<Node> for TestConsensusBuilder
107where
108 Node: FullNodeTypes,
109{
110 type Consensus = Arc<TestConsensus>;
111
112 async fn build_consensus(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
113 Ok(Arc::new(TestConsensus::default()))
114 }
115}
116
117#[derive(Debug, Default, Clone, Copy)]
119#[non_exhaustive]
120pub struct TestNode;
121
122impl NodeTypes for TestNode {
123 type Primitives = EthPrimitives;
124 type ChainSpec = ChainSpec;
125 type Storage = EthStorage;
126 type Payload = EthEngineTypes;
127}
128
129impl<N> Node<N> for TestNode
130where
131 N: FullNodeTypes<Types = Self>,
132{
133 type ComponentsBuilder = ComponentsBuilder<
134 N,
135 TestPoolBuilder,
136 BasicPayloadServiceBuilder<EthereumPayloadBuilder>,
137 EthereumNetworkBuilder,
138 TestExecutorBuilder,
139 TestConsensusBuilder,
140 >;
141 type AddOns =
142 EthereumAddOns<NodeAdapter<N>, EthereumEthApiBuilder, EthereumEngineValidatorBuilder>;
143
144 fn components_builder(&self) -> Self::ComponentsBuilder {
145 ComponentsBuilder::default()
146 .node_types::<N>()
147 .pool(TestPoolBuilder::default())
148 .executor(TestExecutorBuilder::default())
149 .payload(BasicPayloadServiceBuilder::default())
150 .network(EthereumNetworkBuilder::default())
151 .consensus(TestConsensusBuilder::default())
152 }
153
154 fn add_ons(&self) -> Self::AddOns {
155 EthereumAddOns::default()
156 }
157}
158
159pub type TmpDB = Arc<TempDatabase<DatabaseEnv>>;
161pub type Adapter = NodeAdapter<RethFullAdapter<TmpDB, TestNode>>;
164pub type TestExExContext = ExExContext<Adapter>;
166
167#[derive(Debug)]
169pub struct TestExExHandle {
170 pub genesis: RecoveredBlock<reth_ethereum_primitives::Block>,
172 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
174 pub events_rx: UnboundedReceiver<ExExEvent>,
176 pub notifications_tx: Sender<ExExNotification>,
178 pub runtime: Runtime,
180 _wal_directory: TempDir,
182}
183
184impl TestExExHandle {
185 pub async fn send_notification_chain_committed(&self, chain: Chain) -> eyre::Result<()> {
187 self.notifications_tx
188 .send(ExExNotification::ChainCommitted { new: Arc::new(chain) })
189 .await?;
190 Ok(())
191 }
192
193 pub async fn send_notification_chain_reorged(
195 &self,
196 old: Chain,
197 new: Chain,
198 ) -> eyre::Result<()> {
199 self.notifications_tx
200 .send(ExExNotification::ChainReorged { old: Arc::new(old), new: Arc::new(new) })
201 .await?;
202 Ok(())
203 }
204
205 pub async fn send_notification_chain_reverted(&self, chain: Chain) -> eyre::Result<()> {
207 self.notifications_tx
208 .send(ExExNotification::ChainReverted { old: Arc::new(chain) })
209 .await?;
210 Ok(())
211 }
212
213 #[track_caller]
215 pub fn assert_events_empty(&self) {
216 assert!(self.events_rx.is_empty());
217 }
218
219 #[track_caller]
222 pub fn assert_event_finished_height(&mut self, height: BlockNumHash) -> eyre::Result<()> {
223 let event = self.events_rx.try_recv()?;
224 assert_eq!(event, ExExEvent::FinishedHeight(height));
225 Ok(())
226 }
227}
228
229pub async fn test_exex_context_with_chain_spec(
241 chain_spec: Arc<ChainSpec>,
242) -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
243 let transaction_pool = testing_pool();
244 let evm_config = MockEvmConfig::default();
245 let consensus = Arc::new(TestConsensus::default());
246
247 let (static_dir, _) = create_test_static_files_dir();
248 let (rocksdb_dir, _) = create_test_rocksdb_dir();
249 let db = create_test_rw_db();
250 let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<TestNode, _>>::new(
251 db,
252 chain_spec.clone(),
253 StaticFileProvider::read_write(static_dir.keep()).expect("static file provider"),
254 RocksDBProvider::builder(rocksdb_dir.keep()).with_default_tables().build().unwrap(),
255 reth_tasks::Runtime::test(),
256 )?;
257
258 let genesis_hash = init_genesis(&provider_factory)?;
259 let provider = BlockchainProvider::new(provider_factory.clone())?;
260
261 let network_manager = NetworkManager::new(
262 NetworkConfigBuilder::new(rng_secret_key())
263 .with_unused_discovery_port()
264 .with_unused_listener_port()
265 .build(provider_factory.clone()),
266 )
267 .await?;
268 let network = network_manager.handle().clone();
269 let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
270 let task_executor = runtime.clone();
271 runtime.spawn_task(network_manager);
272
273 let (_, payload_builder_handle) = NoopPayloadBuilderService::<EthEngineTypes>::new();
274
275 let components = NodeAdapter::<FullNodeTypesAdapter<_, _, _>, _> {
276 components: Components {
277 transaction_pool,
278 evm_config,
279 consensus,
280 network,
281 payload_builder_handle,
282 },
283 task_executor,
284 provider,
285 };
286
287 let genesis = provider_factory
288 .block_by_hash(genesis_hash)?
289 .ok_or_else(|| eyre::eyre!("genesis block not found"))?
290 .seal_slow()
291 .try_recover()?;
292
293 let head = genesis.num_hash();
294
295 let wal_directory = tempfile::tempdir()?;
296 let wal = Wal::new(wal_directory.path())?;
297
298 let (events_tx, events_rx) = tokio::sync::mpsc::unbounded_channel();
299 let (notifications_tx, notifications_rx) = tokio::sync::mpsc::channel(1);
300 let notifications = ExExNotifications::new(
301 head,
302 components.provider.clone(),
303 components.components.evm_config.clone(),
304 notifications_rx,
305 wal.handle(),
306 );
307
308 let ctx = ExExContext {
309 head,
310 config: NodeConfig::test(),
311 reth_config: reth_config::Config::default(),
312 events: events_tx,
313 notifications,
314 components,
315 };
316
317 Ok((
318 ctx,
319 TestExExHandle {
320 genesis,
321 provider_factory,
322 events_rx,
323 notifications_tx,
324 runtime,
325 _wal_directory: wal_directory,
326 },
327 ))
328}
329
330pub async fn test_exex_context() -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
334 test_exex_context_with_chain_spec(MAINNET.clone()).await
335}
336
337pub trait PollOnce {
339 fn poll_once(&mut self) -> impl Future<Output = Result<(), PollOnceError>> + Send;
349}
350
351#[derive(Error, Debug)]
353pub enum PollOnceError {
354 #[error("Execution Extension future returned Ready, but it should never resolve")]
356 FutureIsReady,
357 #[error(transparent)]
359 FutureError(#[from] eyre::Error),
360}
361
362impl<F: Future<Output = eyre::Result<()>> + Unpin + Send> PollOnce for F {
363 async fn poll_once(&mut self) -> Result<(), PollOnceError> {
364 poll_fn(|cx| match self.poll_unpin(cx) {
365 Poll::Ready(Ok(())) => Poll::Ready(Err(PollOnceError::FutureIsReady)),
366 Poll::Ready(Err(err)) => Poll::Ready(Err(PollOnceError::FutureError(err))),
367 Poll::Pending => Poll::Ready(Ok(())),
368 })
369 .await
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[tokio::test]
378 async fn check_test_context_creation() {
379 let _ = test_exex_context().await.unwrap();
380 }
381}