reth_e2e_test_utils/
setup_builder.rs

1//! Builder for configuring and creating test node setups.
2//!
3//! This module provides a flexible builder API for setting up test nodes with custom
4//! configurations through closures that modify `NodeConfig` and `TreeConfig`.
5
6use crate::{node::NodeTestContext, wallet::Wallet, NodeBuilderHelper, NodeHelperType, TmpDB};
7use reth_chainspec::EthChainSpec;
8use reth_engine_local::LocalPayloadAttributesBuilder;
9use reth_node_builder::{
10    EngineNodeLauncher, NodeBuilder, NodeConfig, NodeHandle, NodeTypes, NodeTypesWithDBAdapter,
11    PayloadAttributesBuilder, PayloadTypes,
12};
13use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs};
14use reth_provider::providers::BlockchainProvider;
15use reth_rpc_server_types::RpcModuleSelection;
16use reth_tasks::TaskManager;
17use std::sync::Arc;
18use tracing::{span, Level};
19
20/// Type alias for tree config modifier closure
21type TreeConfigModifier =
22    Box<dyn Fn(reth_node_api::TreeConfig) -> reth_node_api::TreeConfig + Send + Sync>;
23
24/// Type alias for node config modifier closure
25type NodeConfigModifier<C> = Box<dyn Fn(NodeConfig<C>) -> NodeConfig<C> + Send + Sync>;
26
27/// Builder for configuring and creating test node setups.
28///
29/// This builder allows customizing test node configurations through closures that
30/// modify `NodeConfig` and `TreeConfig`. It avoids code duplication by centralizing
31/// the node creation logic.
32pub struct E2ETestSetupBuilder<N, F>
33where
34    N: NodeBuilderHelper,
35    F: Fn(u64) -> <<N as NodeTypes>::Payload as PayloadTypes>::PayloadBuilderAttributes
36        + Send
37        + Sync
38        + Copy
39        + 'static,
40    LocalPayloadAttributesBuilder<N::ChainSpec>:
41        PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
42{
43    num_nodes: usize,
44    chain_spec: Arc<N::ChainSpec>,
45    attributes_generator: F,
46    connect_nodes: bool,
47    tree_config_modifier: Option<TreeConfigModifier>,
48    node_config_modifier: Option<NodeConfigModifier<N::ChainSpec>>,
49}
50
51impl<N, F> E2ETestSetupBuilder<N, F>
52where
53    N: NodeBuilderHelper,
54    F: Fn(u64) -> <<N as NodeTypes>::Payload as PayloadTypes>::PayloadBuilderAttributes
55        + Send
56        + Sync
57        + Copy
58        + 'static,
59    LocalPayloadAttributesBuilder<N::ChainSpec>:
60        PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
61{
62    /// Creates a new builder with the required parameters.
63    pub fn new(num_nodes: usize, chain_spec: Arc<N::ChainSpec>, attributes_generator: F) -> Self {
64        Self {
65            num_nodes,
66            chain_spec,
67            attributes_generator,
68            connect_nodes: true,
69            tree_config_modifier: None,
70            node_config_modifier: None,
71        }
72    }
73
74    /// Sets whether nodes should be interconnected (default: true).
75    pub const fn with_connect_nodes(mut self, connect_nodes: bool) -> Self {
76        self.connect_nodes = connect_nodes;
77        self
78    }
79
80    /// Sets a modifier function for the tree configuration.
81    ///
82    /// The closure receives the base tree config and returns a modified version.
83    pub fn with_tree_config_modifier<G>(mut self, modifier: G) -> Self
84    where
85        G: Fn(reth_node_api::TreeConfig) -> reth_node_api::TreeConfig + Send + Sync + 'static,
86    {
87        self.tree_config_modifier = Some(Box::new(modifier));
88        self
89    }
90
91    /// Sets a modifier function for the node configuration.
92    ///
93    /// The closure receives the base node config and returns a modified version.
94    pub fn with_node_config_modifier<G>(mut self, modifier: G) -> Self
95    where
96        G: Fn(NodeConfig<N::ChainSpec>) -> NodeConfig<N::ChainSpec> + Send + Sync + 'static,
97    {
98        self.node_config_modifier = Some(Box::new(modifier));
99        self
100    }
101
102    /// Builds and launches the test nodes.
103    pub async fn build(
104        self,
105    ) -> eyre::Result<(
106        Vec<NodeHelperType<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>>,
107        TaskManager,
108        Wallet,
109    )> {
110        let tasks = TaskManager::current();
111        let exec = tasks.executor();
112
113        let network_config = NetworkArgs {
114            discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() },
115            ..NetworkArgs::default()
116        };
117
118        // Apply tree config modifier if present
119        let tree_config = if let Some(modifier) = self.tree_config_modifier {
120            modifier(reth_node_api::TreeConfig::default())
121        } else {
122            reth_node_api::TreeConfig::default()
123        };
124
125        let mut nodes: Vec<NodeTestContext<_, _>> = Vec::with_capacity(self.num_nodes);
126
127        for idx in 0..self.num_nodes {
128            // Create base node config
129            let base_config = NodeConfig::new(self.chain_spec.clone())
130                .with_network(network_config.clone())
131                .with_unused_ports()
132                .with_rpc(
133                    RpcServerArgs::default()
134                        .with_unused_ports()
135                        .with_http()
136                        .with_http_api(RpcModuleSelection::All),
137                );
138
139            // Apply node config modifier if present
140            let node_config = if let Some(modifier) = &self.node_config_modifier {
141                modifier(base_config)
142            } else {
143                base_config
144            };
145
146            let span = span!(Level::INFO, "node", idx);
147            let _enter = span.enter();
148            let node = N::default();
149            let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config)
150                .testing_node(exec.clone())
151                .with_types_and_provider::<N, BlockchainProvider<_>>()
152                .with_components(node.components_builder())
153                .with_add_ons(node.add_ons())
154                .launch_with_fn(|builder| {
155                    let launcher = EngineNodeLauncher::new(
156                        builder.task_executor().clone(),
157                        builder.config().datadir(),
158                        tree_config.clone(),
159                    );
160                    builder.launch_with(launcher)
161                })
162                .await?;
163
164            let mut node = NodeTestContext::new(node, self.attributes_generator).await?;
165
166            let genesis = node.block_hash(0);
167            node.update_forkchoice(genesis, genesis).await?;
168
169            // Connect nodes if requested
170            if self.connect_nodes {
171                if let Some(previous_node) = nodes.last_mut() {
172                    previous_node.connect(&mut node).await;
173                }
174
175                // Connect last node with the first if there are more than two
176                if idx + 1 == self.num_nodes &&
177                    self.num_nodes > 2 &&
178                    let Some(first_node) = nodes.first_mut()
179                {
180                    node.connect(first_node).await;
181                }
182            }
183
184            nodes.push(node);
185        }
186
187        Ok((nodes, tasks, Wallet::default().with_chain_id(self.chain_spec.chain().into())))
188    }
189}
190
191impl<N, F> std::fmt::Debug for E2ETestSetupBuilder<N, F>
192where
193    N: NodeBuilderHelper,
194    F: Fn(u64) -> <<N as NodeTypes>::Payload as PayloadTypes>::PayloadBuilderAttributes
195        + Send
196        + Sync
197        + Copy
198        + 'static,
199    LocalPayloadAttributesBuilder<N::ChainSpec>:
200        PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
201{
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        f.debug_struct("E2ETestSetupBuilder")
204            .field("num_nodes", &self.num_nodes)
205            .field("connect_nodes", &self.connect_nodes)
206            .field("tree_config_modifier", &self.tree_config_modifier.as_ref().map(|_| "<closure>"))
207            .field("node_config_modifier", &self.node_config_modifier.as_ref().map(|_| "<closure>"))
208            .finish_non_exhaustive()
209    }
210}