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::TaskManager;
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> PoolBuilder<Node> for TestPoolBuilder
70where
71 Node: FullNodeTypes<Types: NodeTypes<Primitives: NodePrimitives<SignedTx = TransactionSigned>>>,
72{
73 type Pool = TestPool;
74
75 async fn build_pool(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
76 Ok(testing_pool())
77 }
78}
79
80#[derive(Debug, Default, Clone, Copy)]
82#[non_exhaustive]
83pub struct TestExecutorBuilder;
84
85impl<Node> ExecutorBuilder<Node> for TestExecutorBuilder
86where
87 Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>,
88{
89 type EVM = MockEvmConfig;
90
91 async fn build_evm(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
92 let evm_config = MockEvmConfig::default();
93 Ok(evm_config)
94 }
95}
96
97#[derive(Debug, Default, Clone, Copy)]
99#[non_exhaustive]
100pub struct TestConsensusBuilder;
101
102impl<Node> ConsensusBuilder<Node> for TestConsensusBuilder
103where
104 Node: FullNodeTypes,
105{
106 type Consensus = Arc<TestConsensus>;
107
108 async fn build_consensus(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
109 Ok(Arc::new(TestConsensus::default()))
110 }
111}
112
113#[derive(Debug, Default, Clone, Copy)]
115#[non_exhaustive]
116pub struct TestNode;
117
118impl NodeTypes for TestNode {
119 type Primitives = EthPrimitives;
120 type ChainSpec = ChainSpec;
121 type Storage = EthStorage;
122 type Payload = EthEngineTypes;
123}
124
125impl<N> Node<N> for TestNode
126where
127 N: FullNodeTypes<Types = Self>,
128{
129 type ComponentsBuilder = ComponentsBuilder<
130 N,
131 TestPoolBuilder,
132 BasicPayloadServiceBuilder<EthereumPayloadBuilder>,
133 EthereumNetworkBuilder,
134 TestExecutorBuilder,
135 TestConsensusBuilder,
136 >;
137 type AddOns =
138 EthereumAddOns<NodeAdapter<N>, EthereumEthApiBuilder, EthereumEngineValidatorBuilder>;
139
140 fn components_builder(&self) -> Self::ComponentsBuilder {
141 ComponentsBuilder::default()
142 .node_types::<N>()
143 .pool(TestPoolBuilder::default())
144 .executor(TestExecutorBuilder::default())
145 .payload(BasicPayloadServiceBuilder::default())
146 .network(EthereumNetworkBuilder::default())
147 .consensus(TestConsensusBuilder::default())
148 }
149
150 fn add_ons(&self) -> Self::AddOns {
151 EthereumAddOns::default()
152 }
153}
154
155pub type TmpDB = Arc<TempDatabase<DatabaseEnv>>;
157pub type Adapter = NodeAdapter<RethFullAdapter<TmpDB, TestNode>>;
160pub type TestExExContext = ExExContext<Adapter>;
162
163#[derive(Debug)]
165pub struct TestExExHandle {
166 pub genesis: RecoveredBlock<reth_ethereum_primitives::Block>,
168 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
170 pub events_rx: UnboundedReceiver<ExExEvent>,
172 pub notifications_tx: Sender<ExExNotification>,
174 pub tasks: TaskManager,
176 _wal_directory: TempDir,
178}
179
180impl TestExExHandle {
181 pub async fn send_notification_chain_committed(&self, chain: Chain) -> eyre::Result<()> {
183 self.notifications_tx
184 .send(ExExNotification::ChainCommitted { new: Arc::new(chain) })
185 .await?;
186 Ok(())
187 }
188
189 pub async fn send_notification_chain_reorged(
191 &self,
192 old: Chain,
193 new: Chain,
194 ) -> eyre::Result<()> {
195 self.notifications_tx
196 .send(ExExNotification::ChainReorged { old: Arc::new(old), new: Arc::new(new) })
197 .await?;
198 Ok(())
199 }
200
201 pub async fn send_notification_chain_reverted(&self, chain: Chain) -> eyre::Result<()> {
203 self.notifications_tx
204 .send(ExExNotification::ChainReverted { old: Arc::new(chain) })
205 .await?;
206 Ok(())
207 }
208
209 #[track_caller]
211 pub fn assert_events_empty(&self) {
212 assert!(self.events_rx.is_empty());
213 }
214
215 #[track_caller]
218 pub fn assert_event_finished_height(&mut self, height: BlockNumHash) -> eyre::Result<()> {
219 let event = self.events_rx.try_recv()?;
220 assert_eq!(event, ExExEvent::FinishedHeight(height));
221 Ok(())
222 }
223}
224
225pub async fn test_exex_context_with_chain_spec(
237 chain_spec: Arc<ChainSpec>,
238) -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
239 let transaction_pool = testing_pool();
240 let evm_config = MockEvmConfig::default();
241 let consensus = Arc::new(TestConsensus::default());
242
243 let (static_dir, _) = create_test_static_files_dir();
244 let (rocksdb_dir, _) = create_test_rocksdb_dir();
245 let db = create_test_rw_db();
246 let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<TestNode, _>>::new(
247 db,
248 chain_spec.clone(),
249 StaticFileProvider::read_write(static_dir.keep()).expect("static file provider"),
250 RocksDBProvider::builder(rocksdb_dir.keep()).build().unwrap(),
251 )?;
252
253 let genesis_hash = init_genesis(&provider_factory)?;
254 let provider = BlockchainProvider::new(provider_factory.clone())?;
255
256 let network_manager = NetworkManager::new(
257 NetworkConfigBuilder::new(rng_secret_key())
258 .with_unused_discovery_port()
259 .with_unused_listener_port()
260 .build(provider_factory.clone()),
261 )
262 .await?;
263 let network = network_manager.handle().clone();
264 let tasks = TaskManager::current();
265 let task_executor = tasks.executor();
266 tasks.executor().spawn(network_manager);
267
268 let (_, payload_builder_handle) = NoopPayloadBuilderService::<EthEngineTypes>::new();
269
270 let components = NodeAdapter::<FullNodeTypesAdapter<_, _, _>, _> {
271 components: Components {
272 transaction_pool,
273 evm_config,
274 consensus,
275 network,
276 payload_builder_handle,
277 },
278 task_executor,
279 provider,
280 };
281
282 let genesis = provider_factory
283 .block_by_hash(genesis_hash)?
284 .ok_or_else(|| eyre::eyre!("genesis block not found"))?
285 .seal_slow()
286 .try_recover()?;
287
288 let head = genesis.num_hash();
289
290 let wal_directory = tempfile::tempdir()?;
291 let wal = Wal::new(wal_directory.path())?;
292
293 let (events_tx, events_rx) = tokio::sync::mpsc::unbounded_channel();
294 let (notifications_tx, notifications_rx) = tokio::sync::mpsc::channel(1);
295 let notifications = ExExNotifications::new(
296 head,
297 components.provider.clone(),
298 components.components.evm_config.clone(),
299 notifications_rx,
300 wal.handle(),
301 );
302
303 let ctx = ExExContext {
304 head,
305 config: NodeConfig::test(),
306 reth_config: reth_config::Config::default(),
307 events: events_tx,
308 notifications,
309 components,
310 };
311
312 Ok((
313 ctx,
314 TestExExHandle {
315 genesis,
316 provider_factory,
317 events_rx,
318 notifications_tx,
319 tasks,
320 _wal_directory: wal_directory,
321 },
322 ))
323}
324
325pub async fn test_exex_context() -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
329 test_exex_context_with_chain_spec(MAINNET.clone()).await
330}
331
332pub trait PollOnce {
334 fn poll_once(&mut self) -> impl Future<Output = Result<(), PollOnceError>> + Send;
344}
345
346#[derive(Error, Debug)]
348pub enum PollOnceError {
349 #[error("Execution Extension future returned Ready, but it should never resolve")]
351 FutureIsReady,
352 #[error(transparent)]
354 FutureError(#[from] eyre::Error),
355}
356
357impl<F: Future<Output = eyre::Result<()>> + Unpin + Send> PollOnce for F {
358 async fn poll_once(&mut self) -> Result<(), PollOnceError> {
359 poll_fn(|cx| match self.poll_unpin(cx) {
360 Poll::Ready(Ok(())) => Poll::Ready(Err(PollOnceError::FutureIsReady)),
361 Poll::Ready(Err(err)) => Poll::Ready(Err(PollOnceError::FutureError(err))),
362 Poll::Pending => Poll::Ready(Ok(())),
363 })
364 .await
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 #[tokio::test]
373 async fn check_test_context_creation() {
374 let _ = test_exex_context().await.unwrap();
375 }
376}