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