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/SeismicSystems/seismic-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, 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,
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 = MockEvmConfig;
85
86 async fn build_evm(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
87 let evm_config = MockEvmConfig::default();
88 Ok(evm_config)
89 }
90}
91
92#[derive(Debug, Default, Clone, Copy)]
94#[non_exhaustive]
95pub struct TestConsensusBuilder;
96
97impl<Node> ConsensusBuilder<Node> for TestConsensusBuilder
98where
99 Node: FullNodeTypes,
100{
101 type Consensus = Arc<TestConsensus>;
102
103 async fn build_consensus(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
104 Ok(Arc::new(TestConsensus::default()))
105 }
106}
107
108#[derive(Debug, Default, Clone, Copy)]
110#[non_exhaustive]
111pub struct TestNode;
112
113impl NodeTypes for TestNode {
114 type Primitives = EthPrimitives;
115 type ChainSpec = ChainSpec;
116 type StateCommitment = reth_trie_db::MerklePatriciaTrie;
117 type Storage = EthStorage;
118 type Payload = EthEngineTypes;
119}
120
121impl<N> Node<N> for TestNode
122where
123 N: FullNodeTypes<
124 Types: NodeTypes<
125 Payload = EthEngineTypes,
126 ChainSpec = ChainSpec,
127 Primitives = EthPrimitives,
128 Storage = EthStorage,
129 >,
130 >,
131{
132 type ComponentsBuilder = ComponentsBuilder<
133 N,
134 TestPoolBuilder,
135 BasicPayloadServiceBuilder<EthereumPayloadBuilder>,
136 EthereumNetworkBuilder,
137 TestExecutorBuilder,
138 TestConsensusBuilder,
139 >;
140 type AddOns = EthereumAddOns<
141 NodeAdapter<N, <Self::ComponentsBuilder as NodeComponentsBuilder<N>>::Components>,
142 >;
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<
164 RethFullAdapter<TmpDB, TestNode>,
165 <<TestNode as Node<
166 FullNodeTypesAdapter<
167 TestNode,
168 TmpDB,
169 BlockchainProvider<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
170 >,
171 >>::ComponentsBuilder as NodeComponentsBuilder<RethFullAdapter<TmpDB, TestNode>>>::Components,
172>;
173pub type TestExExContext = ExExContext<Adapter>;
175
176#[derive(Debug)]
178pub struct TestExExHandle {
179 pub genesis: RecoveredBlock<reth_ethereum_primitives::Block>,
181 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
183 pub events_rx: UnboundedReceiver<ExExEvent>,
185 pub notifications_tx: Sender<ExExNotification>,
187 pub tasks: TaskManager,
189 _wal_directory: TempDir,
191}
192
193impl TestExExHandle {
194 pub async fn send_notification_chain_committed(&self, chain: Chain) -> eyre::Result<()> {
196 self.notifications_tx
197 .send(ExExNotification::ChainCommitted { new: Arc::new(chain) })
198 .await?;
199 Ok(())
200 }
201
202 pub async fn send_notification_chain_reorged(
204 &self,
205 old: Chain,
206 new: Chain,
207 ) -> eyre::Result<()> {
208 self.notifications_tx
209 .send(ExExNotification::ChainReorged { old: Arc::new(old), new: Arc::new(new) })
210 .await?;
211 Ok(())
212 }
213
214 pub async fn send_notification_chain_reverted(&self, chain: Chain) -> eyre::Result<()> {
216 self.notifications_tx
217 .send(ExExNotification::ChainReverted { old: Arc::new(chain) })
218 .await?;
219 Ok(())
220 }
221
222 #[track_caller]
224 pub fn assert_events_empty(&self) {
225 assert!(self.events_rx.is_empty());
226 }
227
228 #[track_caller]
231 pub fn assert_event_finished_height(&mut self, height: BlockNumHash) -> eyre::Result<()> {
232 let event = self.events_rx.try_recv()?;
233 assert_eq!(event, ExExEvent::FinishedHeight(height));
234 Ok(())
235 }
236}
237
238pub async fn test_exex_context_with_chain_spec(
250 chain_spec: Arc<ChainSpec>,
251) -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
252 let transaction_pool = testing_pool();
253 let evm_config = MockEvmConfig::default();
254 let consensus = Arc::new(TestConsensus::default());
255
256 let (static_dir, _) = create_test_static_files_dir();
257 let db = create_test_rw_db();
258 let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<TestNode, _>>::new(
259 db,
260 chain_spec.clone(),
261 StaticFileProvider::read_write(static_dir.keep()).expect("static file provider"),
262 );
263
264 let genesis_hash = init_genesis(&provider_factory)?;
265 let provider = BlockchainProvider::new(provider_factory.clone())?;
266
267 let network_manager = NetworkManager::new(
268 NetworkConfigBuilder::new(rng_secret_key())
269 .with_unused_discovery_port()
270 .with_unused_listener_port()
271 .build(provider_factory.clone()),
272 )
273 .await?;
274 let network = network_manager.handle().clone();
275 let tasks = TaskManager::current();
276 let task_executor = tasks.executor();
277 tasks.executor().spawn(network_manager);
278
279 let (_, payload_builder_handle) = NoopPayloadBuilderService::<EthEngineTypes>::new();
280
281 let components = NodeAdapter::<FullNodeTypesAdapter<_, _, _>, _> {
282 components: Components {
283 transaction_pool,
284 evm_config,
285 consensus,
286 network,
287 payload_builder_handle,
288 },
289 task_executor,
290 provider,
291 };
292
293 let genesis = provider_factory
294 .block_by_hash(genesis_hash)?
295 .ok_or_else(|| eyre::eyre!("genesis block not found"))?
296 .seal_slow()
297 .try_recover()?;
298
299 let head = genesis.num_hash();
300
301 let wal_directory = tempfile::tempdir()?;
302 let wal = Wal::new(wal_directory.path())?;
303
304 let (events_tx, events_rx) = tokio::sync::mpsc::unbounded_channel();
305 let (notifications_tx, notifications_rx) = tokio::sync::mpsc::channel(1);
306 let notifications = ExExNotifications::new(
307 head,
308 components.provider.clone(),
309 components.components.evm_config.clone(),
310 notifications_rx,
311 wal.handle(),
312 );
313
314 let ctx = ExExContext {
315 head,
316 config: NodeConfig::test(),
317 reth_config: reth_config::Config::default(),
318 events: events_tx,
319 notifications,
320 components,
321 };
322
323 Ok((
324 ctx,
325 TestExExHandle {
326 genesis,
327 provider_factory,
328 events_rx,
329 notifications_tx,
330 tasks,
331 _wal_directory: wal_directory,
332 },
333 ))
334}
335
336pub async fn test_exex_context() -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
340 test_exex_context_with_chain_spec(MAINNET.clone()).await
341}
342
343pub trait PollOnce {
345 fn poll_once(&mut self) -> impl Future<Output = Result<(), PollOnceError>> + Send;
355}
356
357#[derive(Error, Debug)]
359pub enum PollOnceError {
360 #[error("Execution Extension future returned Ready, but it should never resolve")]
362 FutureIsReady,
363 #[error(transparent)]
365 FutureError(#[from] eyre::Error),
366}
367
368impl<F: Future<Output = eyre::Result<()>> + Unpin + Send> PollOnce for F {
369 async fn poll_once(&mut self) -> Result<(), PollOnceError> {
370 poll_fn(|cx| match self.poll_unpin(cx) {
371 Poll::Ready(Ok(())) => Poll::Ready(Err(PollOnceError::FutureIsReady)),
372 Poll::Ready(Err(err)) => Poll::Ready(Err(PollOnceError::FutureError(err))),
373 Poll::Pending => Poll::Ready(Ok(())),
374 })
375 .await
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[tokio::test]
384 async fn check_test_context_creation() {
385 let _ = test_exex_context().await.unwrap();
386 }
387}