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::test_utils::MockExecutorProvider;
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, NodeComponentsBuilder, PoolBuilder,
39 },
40 BuilderContext, Node, NodeAdapter, RethFullAdapter,
41};
42use reth_node_core::node_config::NodeConfig;
43use reth_node_ethereum::{
44 node::{EthereumAddOns, EthereumNetworkBuilder, EthereumPayloadBuilder},
45 EthEngineTypes, EthEvmConfig,
46};
47use reth_payload_builder::noop::NoopPayloadBuilderService;
48use reth_primitives_traits::{Block as _, RecoveredBlock};
49use reth_provider::{
50 providers::{BlockchainProvider, StaticFileProvider},
51 BlockReader, EthStorage, ProviderFactory,
52};
53use reth_tasks::TaskManager;
54use reth_transaction_pool::test_utils::{testing_pool, TestPool};
55use tempfile::TempDir;
56use thiserror::Error;
57use tokio::sync::mpsc::{Sender, UnboundedReceiver};
58
59#[derive(Debug, Default, Clone, Copy)]
61#[non_exhaustive]
62pub struct TestPoolBuilder;
63
64impl<Node> PoolBuilder<Node> for TestPoolBuilder
65where
66 Node: FullNodeTypes<Types: NodeTypes<Primitives: NodePrimitives<SignedTx = TransactionSigned>>>,
67{
68 type Pool = TestPool;
69
70 async fn build_pool(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
71 Ok(testing_pool())
72 }
73}
74
75#[derive(Debug, Default, Clone, Copy)]
77#[non_exhaustive]
78pub struct TestExecutorBuilder;
79
80impl<Node> ExecutorBuilder<Node> for TestExecutorBuilder
81where
82 Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>,
83{
84 type EVM = EthEvmConfig;
85 type Executor = MockExecutorProvider;
86
87 async fn build_evm(
88 self,
89 ctx: &BuilderContext<Node>,
90 ) -> eyre::Result<(Self::EVM, Self::Executor)> {
91 let evm_config = EthEvmConfig::new(ctx.chain_spec());
92 let executor = MockExecutorProvider::default();
93
94 Ok((evm_config, executor))
95 }
96}
97
98#[derive(Debug, Default, Clone, Copy)]
100#[non_exhaustive]
101pub struct TestConsensusBuilder;
102
103impl<Node> ConsensusBuilder<Node> for TestConsensusBuilder
104where
105 Node: FullNodeTypes,
106{
107 type Consensus = Arc<TestConsensus>;
108
109 async fn build_consensus(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
110 Ok(Arc::new(TestConsensus::default()))
111 }
112}
113
114#[derive(Debug, Default, Clone, Copy)]
116#[non_exhaustive]
117pub struct TestNode;
118
119impl NodeTypes for TestNode {
120 type Primitives = EthPrimitives;
121 type ChainSpec = ChainSpec;
122 type StateCommitment = reth_trie_db::MerklePatriciaTrie;
123 type Storage = EthStorage;
124 type Payload = EthEngineTypes;
125}
126
127impl<N> Node<N> for TestNode
128where
129 N: FullNodeTypes<
130 Types: NodeTypes<
131 Payload = EthEngineTypes,
132 ChainSpec = ChainSpec,
133 Primitives = EthPrimitives,
134 Storage = EthStorage,
135 >,
136 >,
137{
138 type ComponentsBuilder = ComponentsBuilder<
139 N,
140 TestPoolBuilder,
141 BasicPayloadServiceBuilder<EthereumPayloadBuilder>,
142 EthereumNetworkBuilder,
143 TestExecutorBuilder,
144 TestConsensusBuilder,
145 >;
146 type AddOns = EthereumAddOns<
147 NodeAdapter<N, <Self::ComponentsBuilder as NodeComponentsBuilder<N>>::Components>,
148 >;
149
150 fn components_builder(&self) -> Self::ComponentsBuilder {
151 ComponentsBuilder::default()
152 .node_types::<N>()
153 .pool(TestPoolBuilder::default())
154 .payload(BasicPayloadServiceBuilder::default())
155 .network(EthereumNetworkBuilder::default())
156 .executor(TestExecutorBuilder::default())
157 .consensus(TestConsensusBuilder::default())
158 }
159
160 fn add_ons(&self) -> Self::AddOns {
161 EthereumAddOns::default()
162 }
163}
164
165pub type TmpDB = Arc<TempDatabase<DatabaseEnv>>;
167pub type Adapter = NodeAdapter<
170 RethFullAdapter<TmpDB, TestNode>,
171 <<TestNode as Node<
172 FullNodeTypesAdapter<
173 TestNode,
174 TmpDB,
175 BlockchainProvider<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
176 >,
177 >>::ComponentsBuilder as NodeComponentsBuilder<RethFullAdapter<TmpDB, TestNode>>>::Components,
178>;
179pub type TestExExContext = ExExContext<Adapter>;
181
182#[derive(Debug)]
184pub struct TestExExHandle {
185 pub genesis: RecoveredBlock<reth_ethereum_primitives::Block>,
187 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
189 pub events_rx: UnboundedReceiver<ExExEvent>,
191 pub notifications_tx: Sender<ExExNotification>,
193 pub tasks: TaskManager,
195 _wal_directory: TempDir,
197}
198
199impl TestExExHandle {
200 pub async fn send_notification_chain_committed(&self, chain: Chain) -> eyre::Result<()> {
202 self.notifications_tx
203 .send(ExExNotification::ChainCommitted { new: Arc::new(chain) })
204 .await?;
205 Ok(())
206 }
207
208 pub async fn send_notification_chain_reorged(
210 &self,
211 old: Chain,
212 new: Chain,
213 ) -> eyre::Result<()> {
214 self.notifications_tx
215 .send(ExExNotification::ChainReorged { old: Arc::new(old), new: Arc::new(new) })
216 .await?;
217 Ok(())
218 }
219
220 pub async fn send_notification_chain_reverted(&self, chain: Chain) -> eyre::Result<()> {
222 self.notifications_tx
223 .send(ExExNotification::ChainReverted { old: Arc::new(chain) })
224 .await?;
225 Ok(())
226 }
227
228 #[track_caller]
230 pub fn assert_events_empty(&self) {
231 assert!(self.events_rx.is_empty());
232 }
233
234 #[track_caller]
237 pub fn assert_event_finished_height(&mut self, height: BlockNumHash) -> eyre::Result<()> {
238 let event = self.events_rx.try_recv()?;
239 assert_eq!(event, ExExEvent::FinishedHeight(height));
240 Ok(())
241 }
242}
243
244pub async fn test_exex_context_with_chain_spec(
256 chain_spec: Arc<ChainSpec>,
257) -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
258 let transaction_pool = testing_pool();
259 let evm_config = EthEvmConfig::new(chain_spec.clone());
260 let executor = MockExecutorProvider::default();
261 let consensus = Arc::new(TestConsensus::default());
262
263 let (static_dir, _) = create_test_static_files_dir();
264 let db = create_test_rw_db();
265 let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<TestNode, _>>::new(
266 db,
267 chain_spec.clone(),
268 StaticFileProvider::read_write(static_dir.into_path()).expect("static file provider"),
269 );
270
271 let genesis_hash = init_genesis(&provider_factory)?;
272 let provider = BlockchainProvider::new(provider_factory.clone())?;
273
274 let network_manager = NetworkManager::new(
275 NetworkConfigBuilder::new(rng_secret_key())
276 .with_unused_discovery_port()
277 .with_unused_listener_port()
278 .build(provider_factory.clone()),
279 )
280 .await?;
281 let network = network_manager.handle().clone();
282 let tasks = TaskManager::current();
283 let task_executor = tasks.executor();
284 tasks.executor().spawn(network_manager);
285
286 let (_, payload_builder_handle) = NoopPayloadBuilderService::<EthEngineTypes>::new();
287
288 let components = NodeAdapter::<FullNodeTypesAdapter<_, _, _>, _> {
289 components: Components {
290 transaction_pool,
291 evm_config,
292 executor,
293 consensus,
294 network,
295 payload_builder_handle,
296 },
297 task_executor,
298 provider,
299 };
300
301 let genesis = provider_factory
302 .block_by_hash(genesis_hash)?
303 .ok_or_else(|| eyre::eyre!("genesis block not found"))?
304 .seal_slow()
305 .try_recover()?;
306
307 let head = genesis.num_hash();
308
309 let wal_directory = tempfile::tempdir()?;
310 let wal = Wal::new(wal_directory.path())?;
311
312 let (events_tx, events_rx) = tokio::sync::mpsc::unbounded_channel();
313 let (notifications_tx, notifications_rx) = tokio::sync::mpsc::channel(1);
314 let notifications = ExExNotifications::new(
315 head,
316 components.provider.clone(),
317 components.components.executor.clone(),
318 notifications_rx,
319 wal.handle(),
320 );
321
322 let ctx = ExExContext {
323 head,
324 config: NodeConfig::test(),
325 reth_config: reth_config::Config::default(),
326 events: events_tx,
327 notifications,
328 components,
329 };
330
331 Ok((
332 ctx,
333 TestExExHandle {
334 genesis,
335 provider_factory,
336 events_rx,
337 notifications_tx,
338 tasks,
339 _wal_directory: wal_directory,
340 },
341 ))
342}
343
344pub async fn test_exex_context() -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
348 test_exex_context_with_chain_spec(MAINNET.clone()).await
349}
350
351pub trait PollOnce {
353 fn poll_once(&mut self) -> impl Future<Output = Result<(), PollOnceError>> + Send;
363}
364
365#[derive(Error, Debug)]
367pub enum PollOnceError {
368 #[error("Execution Extension future returned Ready, but it should never resolve")]
370 FutureIsReady,
371 #[error(transparent)]
373 FutureError(#[from] eyre::Error),
374}
375
376impl<F: Future<Output = eyre::Result<()>> + Unpin + Send> PollOnce for F {
377 async fn poll_once(&mut self) -> Result<(), PollOnceError> {
378 poll_fn(|cx| match self.poll_unpin(cx) {
379 Poll::Ready(Ok(())) => Poll::Ready(Err(PollOnceError::FutureIsReady)),
380 Poll::Ready(Err(err)) => Poll::Ready(Err(PollOnceError::FutureError(err))),
381 Poll::Pending => Poll::Ready(Ok(())),
382 })
383 .await
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[tokio::test]
392 async fn check_test_context_creation() {
393 let _ = test_exex_context().await.unwrap();
394 }
395}