reth_e2e_test_utils/testsuite/
mod.rs1use crate::{
4 testsuite::actions::{Action, ActionBox},
5 NodeBuilderHelper,
6};
7use alloy_primitives::B256;
8use eyre::Result;
9use jsonrpsee::http_client::HttpClient;
10use reth_node_api::{EngineTypes, PayloadTypes};
11use reth_payload_builder::PayloadId;
12use std::{collections::HashMap, marker::PhantomData};
13pub mod actions;
14pub mod setup;
15use crate::testsuite::setup::Setup;
16use alloy_provider::{Provider, ProviderBuilder};
17use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes};
18use reth_engine_primitives::ConsensusEngineHandle;
19use reth_rpc_builder::auth::AuthServerHandle;
20use std::sync::Arc;
21use url::Url;
22
23#[derive(Clone)]
25pub struct NodeClient<Payload>
26where
27 Payload: PayloadTypes,
28{
29 pub rpc: HttpClient,
31 pub engine: AuthServerHandle,
33 pub beacon_engine_handle: Option<ConsensusEngineHandle<Payload>>,
35 provider: Arc<dyn Provider + Send + Sync>,
37}
38
39impl<Payload> NodeClient<Payload>
40where
41 Payload: PayloadTypes,
42{
43 pub fn new(rpc: HttpClient, engine: AuthServerHandle, url: Url) -> Self {
45 let provider =
46 Arc::new(ProviderBuilder::new().connect_http(url)) as Arc<dyn Provider + Send + Sync>;
47 Self { rpc, engine, beacon_engine_handle: None, provider }
48 }
49
50 pub fn new_with_beacon_engine(
52 rpc: HttpClient,
53 engine: AuthServerHandle,
54 url: Url,
55 beacon_engine_handle: ConsensusEngineHandle<Payload>,
56 ) -> Self {
57 let provider =
58 Arc::new(ProviderBuilder::new().connect_http(url)) as Arc<dyn Provider + Send + Sync>;
59 Self { rpc, engine, beacon_engine_handle: Some(beacon_engine_handle), provider }
60 }
61
62 pub async fn get_block_by_number(
64 &self,
65 number: alloy_eips::BlockNumberOrTag,
66 ) -> Result<Option<alloy_rpc_types_eth::Block>> {
67 self.provider
68 .get_block_by_number(number)
69 .await
70 .map_err(|e| eyre::eyre!("Failed to get block by number: {}", e))
71 }
72
73 pub async fn is_ready(&self) -> bool {
75 self.get_block_by_number(alloy_eips::BlockNumberOrTag::Latest).await.is_ok()
76 }
77}
78
79impl<Payload> std::fmt::Debug for NodeClient<Payload>
80where
81 Payload: PayloadTypes,
82{
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.debug_struct("NodeClient")
85 .field("rpc", &self.rpc)
86 .field("engine", &self.engine)
87 .field("beacon_engine_handle", &self.beacon_engine_handle.is_some())
88 .field("provider", &"<Provider>")
89 .finish()
90 }
91}
92
93#[derive(Debug, Clone, Copy)]
95pub struct BlockInfo {
96 pub hash: B256,
98 pub number: u64,
100 pub timestamp: u64,
102}
103
104#[derive(Clone)]
106pub struct NodeState<I>
107where
108 I: EngineTypes,
109{
110 pub current_block_info: Option<BlockInfo>,
112 pub payload_attributes: HashMap<u64, PayloadAttributes>,
114 pub latest_header_time: u64,
116 pub payload_id_history: HashMap<u64, PayloadId>,
118 pub next_payload_id: Option<PayloadId>,
120 pub latest_fork_choice_state: ForkchoiceState,
122 pub latest_payload_built: Option<PayloadAttributes>,
124 pub latest_payload_executed: Option<PayloadAttributes>,
126 pub latest_payload_envelope: Option<I::ExecutionPayloadEnvelopeV3>,
128 pub current_fork_base: Option<u64>,
130}
131
132impl<I> Default for NodeState<I>
133where
134 I: EngineTypes,
135{
136 fn default() -> Self {
137 Self {
138 current_block_info: None,
139 payload_attributes: HashMap::new(),
140 latest_header_time: 0,
141 payload_id_history: HashMap::new(),
142 next_payload_id: None,
143 latest_fork_choice_state: ForkchoiceState::default(),
144 latest_payload_built: None,
145 latest_payload_executed: None,
146 latest_payload_envelope: None,
147 current_fork_base: None,
148 }
149 }
150}
151
152impl<I> std::fmt::Debug for NodeState<I>
153where
154 I: EngineTypes,
155{
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 f.debug_struct("NodeState")
158 .field("current_block_info", &self.current_block_info)
159 .field("payload_attributes", &self.payload_attributes)
160 .field("latest_header_time", &self.latest_header_time)
161 .field("payload_id_history", &self.payload_id_history)
162 .field("next_payload_id", &self.next_payload_id)
163 .field("latest_fork_choice_state", &self.latest_fork_choice_state)
164 .field("latest_payload_built", &self.latest_payload_built)
165 .field("latest_payload_executed", &self.latest_payload_executed)
166 .field("latest_payload_envelope", &"<ExecutionPayloadEnvelopeV3>")
167 .field("current_fork_base", &self.current_fork_base)
168 .finish()
169 }
170}
171
172#[derive(Debug)]
174pub struct Environment<I>
175where
176 I: EngineTypes,
177{
178 pub node_clients: Vec<NodeClient<I>>,
180 pub node_states: Vec<NodeState<I>>,
182 _phantom: PhantomData<I>,
184 pub last_producer_idx: Option<usize>,
186 pub block_timestamp_increment: u64,
188 pub slots_to_safe: u64,
190 pub slots_to_finalized: u64,
192 pub block_registry: HashMap<String, (BlockInfo, usize)>,
194 pub active_node_idx: usize,
196}
197
198impl<I> Default for Environment<I>
199where
200 I: EngineTypes,
201{
202 fn default() -> Self {
203 Self {
204 node_clients: vec![],
205 node_states: vec![],
206 _phantom: Default::default(),
207 last_producer_idx: None,
208 block_timestamp_increment: 2,
209 slots_to_safe: 0,
210 slots_to_finalized: 0,
211 block_registry: HashMap::new(),
212 active_node_idx: 0,
213 }
214 }
215}
216
217impl<I> Environment<I>
218where
219 I: EngineTypes,
220{
221 pub const fn node_count(&self) -> usize {
223 self.node_clients.len()
224 }
225
226 pub fn node_state_mut(&mut self, node_idx: usize) -> Result<&mut NodeState<I>, eyre::Error> {
228 let node_count = self.node_count();
229 self.node_states.get_mut(node_idx).ok_or_else(|| {
230 eyre::eyre!("Node index {} out of bounds (have {} nodes)", node_idx, node_count)
231 })
232 }
233
234 pub fn node_state(&self, node_idx: usize) -> Result<&NodeState<I>, eyre::Error> {
236 self.node_states.get(node_idx).ok_or_else(|| {
237 eyre::eyre!("Node index {} out of bounds (have {} nodes)", node_idx, self.node_count())
238 })
239 }
240
241 pub fn active_node_state(&self) -> Result<&NodeState<I>, eyre::Error> {
243 self.node_state(self.active_node_idx)
244 }
245
246 pub fn active_node_state_mut(&mut self) -> Result<&mut NodeState<I>, eyre::Error> {
248 let idx = self.active_node_idx;
249 self.node_state_mut(idx)
250 }
251
252 pub fn set_active_node(&mut self, node_idx: usize) -> Result<(), eyre::Error> {
254 if node_idx >= self.node_count() {
255 return Err(eyre::eyre!(
256 "Node index {} out of bounds (have {} nodes)",
257 node_idx,
258 self.node_count()
259 ));
260 }
261 self.active_node_idx = node_idx;
262 Ok(())
263 }
264
265 pub fn initialize_node_states(&mut self, node_count: usize) {
267 self.node_states = (0..node_count).map(|_| NodeState::default()).collect();
268 }
269
270 pub fn current_block_info(&self) -> Option<BlockInfo> {
272 self.active_node_state().ok()?.current_block_info
273 }
274
275 pub fn set_current_block_info(&mut self, block_info: BlockInfo) -> Result<(), eyre::Error> {
277 self.active_node_state_mut()?.current_block_info = Some(block_info);
278 Ok(())
279 }
280}
281
282#[expect(missing_debug_implementations)]
284pub struct TestBuilder<I>
285where
286 I: EngineTypes,
287{
288 setup: Option<Setup<I>>,
289 actions: Vec<ActionBox<I>>,
290 env: Environment<I>,
291}
292
293impl<I> Default for TestBuilder<I>
294where
295 I: EngineTypes,
296{
297 fn default() -> Self {
298 Self { setup: None, actions: Vec::new(), env: Default::default() }
299 }
300}
301
302impl<I> TestBuilder<I>
303where
304 I: EngineTypes + 'static,
305{
306 pub fn new() -> Self {
308 Self::default()
309 }
310
311 pub fn with_setup(mut self, setup: Setup<I>) -> Self {
313 self.setup = Some(setup);
314 self
315 }
316
317 pub fn with_setup_and_import(
319 mut self,
320 mut setup: Setup<I>,
321 rlp_path: impl Into<std::path::PathBuf>,
322 ) -> Self {
323 setup.import_rlp_path = Some(rlp_path.into());
324 self.setup = Some(setup);
325 self
326 }
327
328 pub fn with_action<A>(mut self, action: A) -> Self
330 where
331 A: Action<I>,
332 {
333 self.actions.push(ActionBox::<I>::new(action));
334 self
335 }
336
337 pub fn with_actions<II, A>(mut self, actions: II) -> Self
339 where
340 II: IntoIterator<Item = A>,
341 A: Action<I>,
342 {
343 self.actions.extend(actions.into_iter().map(ActionBox::new));
344 self
345 }
346
347 pub async fn run<N>(mut self) -> Result<()>
349 where
350 N: NodeBuilderHelper<Payload = I>,
351 {
352 let mut setup = self.setup.take();
353
354 if let Some(ref mut s) = setup {
355 s.apply::<N>(&mut self.env).await?;
356 }
357
358 let actions = std::mem::take(&mut self.actions);
359
360 for action in actions {
361 action.execute(&mut self.env).await?;
362 }
363
364 drop(setup);
367
368 Ok(())
369 }
370}