reth_e2e_test_utils/testsuite/
setup.rs
1use crate::{setup_engine, testsuite::Environment, NodeBuilderHelper, PayloadAttributesBuilder};
4use alloy_eips::BlockNumberOrTag;
5use alloy_primitives::B256;
6use alloy_rpc_types_engine::PayloadAttributes;
7use alloy_rpc_types_eth::{Block as RpcBlock, Header, Receipt, Transaction};
8use eyre::{eyre, Result};
9use reth_chainspec::ChainSpec;
10use reth_engine_local::LocalPayloadAttributesBuilder;
11use reth_ethereum_primitives::Block;
12use reth_node_api::{NodeTypes, PayloadTypes};
13use reth_node_core::primitives::RecoveredBlock;
14use reth_payload_builder::EthPayloadBuilderAttributes;
15use reth_rpc_api::clients::EthApiClient;
16use revm::state::EvmState;
17use std::{marker::PhantomData, sync::Arc};
18use tokio::{
19 sync::mpsc,
20 time::{sleep, Duration},
21};
22use tracing::{debug, error};
23
24#[derive(Debug)]
26pub struct Setup<I> {
27 pub chain_spec: Option<Arc<ChainSpec>>,
29 pub genesis: Option<Genesis>,
31 pub blocks: Vec<RecoveredBlock<Block>>,
33 pub state: Option<EvmState>,
35 pub network: NetworkSetup,
37 shutdown_tx: Option<mpsc::Sender<()>>,
39 pub is_dev: bool,
41 _phantom: PhantomData<I>,
43}
44
45impl<I> Default for Setup<I> {
46 fn default() -> Self {
47 Self {
48 chain_spec: None,
49 genesis: None,
50 blocks: Vec::new(),
51 state: None,
52 network: NetworkSetup::default(),
53 shutdown_tx: None,
54 is_dev: true,
55 _phantom: Default::default(),
56 }
57 }
58}
59
60impl<I> Drop for Setup<I> {
61 fn drop(&mut self) {
62 if let Some(tx) = self.shutdown_tx.take() {
64 let _ = tx.try_send(());
65 }
66 }
67}
68
69impl<I> Setup<I> {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn with_chain_spec(mut self, chain_spec: Arc<ChainSpec>) -> Self {
77 self.chain_spec = Some(chain_spec);
78 self
79 }
80
81 pub const fn with_genesis(mut self, genesis: Genesis) -> Self {
83 self.genesis = Some(genesis);
84 self
85 }
86
87 pub fn with_block(mut self, block: RecoveredBlock<Block>) -> Self {
89 self.blocks.push(block);
90 self
91 }
92
93 pub fn with_blocks(mut self, blocks: Vec<RecoveredBlock<Block>>) -> Self {
95 self.blocks.extend(blocks);
96 self
97 }
98
99 pub fn with_state(mut self, state: EvmState) -> Self {
101 self.state = Some(state);
102 self
103 }
104
105 pub const fn with_network(mut self, network: NetworkSetup) -> Self {
107 self.network = network;
108 self
109 }
110
111 pub const fn with_dev_mode(mut self, is_dev: bool) -> Self {
113 self.is_dev = is_dev;
114 self
115 }
116
117 pub async fn apply<N>(&mut self, env: &mut Environment<I>) -> Result<()>
119 where
120 N: NodeBuilderHelper,
121 LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
122 <<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
123 >,
124 {
125 let chain_spec =
126 self.chain_spec.clone().ok_or_else(|| eyre!("Chain specification is required"))?;
127
128 let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
129
130 self.shutdown_tx = Some(shutdown_tx);
131
132 let is_dev = self.is_dev;
133 let node_count = self.network.node_count;
134
135 let attributes_generator = move |timestamp| {
136 let attributes = PayloadAttributes {
137 timestamp,
138 prev_randao: B256::ZERO,
139 suggested_fee_recipient: alloy_primitives::Address::ZERO,
140 withdrawals: Some(vec![]),
141 parent_beacon_block_root: Some(B256::ZERO),
142 };
143 <<N as NodeTypes>::Payload as PayloadTypes>::PayloadBuilderAttributes::from(
144 EthPayloadBuilderAttributes::new(B256::ZERO, attributes),
145 )
146 };
147
148 let result = setup_engine::<N>(
149 node_count,
150 Arc::<N::ChainSpec>::new((*chain_spec).clone().into()),
151 is_dev,
152 attributes_generator,
153 )
154 .await;
155
156 let mut node_clients = Vec::new();
157 match result {
158 Ok((nodes, executor, _wallet)) => {
159 for node in &nodes {
161 let rpc = node
162 .rpc_client()
163 .ok_or_else(|| eyre!("Failed to create HTTP RPC client for node"))?;
164 let engine = node.engine_api_client();
165
166 node_clients.push(crate::testsuite::NodeClient { rpc, engine });
167 }
168
169 tokio::spawn(async move {
171 let _nodes = nodes;
173 let _executor = executor;
174 let _ = shutdown_rx.recv().await;
176 });
178 }
179 Err(e) => {
180 error!("Failed to setup nodes: {}", e);
181 return Err(eyre!("Failed to setup nodes: {}", e));
182 }
183 }
184
185 if node_clients.is_empty() {
186 return Err(eyre!("No nodes were created"));
187 }
188
189 for (idx, client) in node_clients.iter().enumerate() {
191 let mut retry_count = 0;
192 const MAX_RETRIES: usize = 5;
193 let mut last_error = None;
194
195 while retry_count < MAX_RETRIES {
196 match EthApiClient::<Transaction, RpcBlock, Receipt, Header>::block_by_number(
197 &client.rpc,
198 BlockNumberOrTag::Latest,
199 false,
200 )
201 .await
202 {
203 Ok(_) => {
204 debug!("Node {idx} RPC endpoint is ready");
205 break;
206 }
207 Err(e) => {
208 last_error = Some(e);
209 retry_count += 1;
210 debug!(
211 "Node {idx} RPC endpoint not ready, retry {retry_count}/{MAX_RETRIES}"
212 );
213 sleep(Duration::from_millis(500)).await;
214 }
215 }
216 }
217 if retry_count == MAX_RETRIES {
218 return Err(eyre!("Failed to connect to node {idx} RPC endpoint after {MAX_RETRIES} retries: {:?}", last_error));
219 }
220 }
221
222 env.node_clients = node_clients;
223
224 Ok(())
227 }
228}
229
230#[derive(Debug)]
232pub struct Genesis {}
233
234#[derive(Debug, Default)]
236pub struct NetworkSetup {
237 pub node_count: usize,
239}
240
241impl NetworkSetup {
242 pub const fn single_node() -> Self {
244 Self { node_count: 1 }
245 }
246
247 pub const fn multi_node(count: usize) -> Self {
249 Self { node_count: count }
250 }
251}