reth_e2e_test_utils/testsuite/
mod.rs

1//! Utilities for running e2e tests against a node or a network of nodes.
2
3use 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::{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_rpc_types_engine::{ForkchoiceState, PayloadAttributes};
18use reth_rpc_builder::auth::AuthServerHandle;
19
20#[cfg(test)]
21mod examples;
22
23/// Client handles for both regular RPC and Engine API endpoints
24#[derive(Debug)]
25pub struct NodeClient {
26    /// Regular JSON-RPC client
27    pub rpc: HttpClient,
28    /// Engine API client
29    pub engine: AuthServerHandle,
30}
31
32impl NodeClient {
33    /// Instantiates a new [`NodeClient`] with the given handles
34    pub const fn new(rpc: HttpClient, engine: AuthServerHandle) -> Self {
35        Self { rpc, engine }
36    }
37}
38
39/// Represents the latest block information.
40#[derive(Debug, Clone)]
41pub struct LatestBlockInfo {
42    /// Hash of the latest block
43    pub hash: B256,
44    /// Number of the latest block
45    pub number: u64,
46}
47/// Represents a test environment.
48#[derive(Debug)]
49pub struct Environment<I> {
50    /// Combined clients with both RPC and Engine API endpoints
51    pub node_clients: Vec<NodeClient>,
52    /// Tracks instance generic.
53    _phantom: PhantomData<I>,
54    /// Latest block information
55    pub latest_block_info: Option<LatestBlockInfo>,
56    /// Last producer index
57    pub last_producer_idx: Option<usize>,
58    /// Stores payload attributes indexed by block number
59    pub payload_attributes: HashMap<u64, PayloadAttributes>,
60    /// Tracks the latest block header timestamp
61    pub latest_header_time: u64,
62    /// Defines the increment for block timestamps (default: 2 seconds)
63    pub block_timestamp_increment: u64,
64    /// Stores payload IDs returned by block producers, indexed by block number
65    pub payload_id_history: HashMap<u64, PayloadId>,
66    /// Stores the next expected payload ID
67    pub next_payload_id: Option<PayloadId>,
68    /// Stores the latest fork choice state
69    pub latest_fork_choice_state: ForkchoiceState,
70    /// Stores the most recent built execution payload
71    pub latest_payload_built: Option<PayloadAttributes>,
72    /// Stores the most recent executed payload
73    pub latest_payload_executed: Option<PayloadAttributes>,
74    /// Number of slots until a block is considered safe
75    pub slots_to_safe: u64,
76    /// Number of slots until a block is considered finalized
77    pub slots_to_finalized: u64,
78}
79
80impl<I> Default for Environment<I> {
81    fn default() -> Self {
82        Self {
83            node_clients: vec![],
84            _phantom: Default::default(),
85            latest_block_info: None,
86            last_producer_idx: None,
87            payload_attributes: Default::default(),
88            latest_header_time: 0,
89            block_timestamp_increment: 2,
90            payload_id_history: HashMap::new(),
91            next_payload_id: None,
92            latest_fork_choice_state: ForkchoiceState::default(),
93            latest_payload_built: None,
94            latest_payload_executed: None,
95            slots_to_safe: 0,
96            slots_to_finalized: 0,
97        }
98    }
99}
100
101/// Builder for creating test scenarios
102#[expect(missing_debug_implementations)]
103#[derive(Default)]
104pub struct TestBuilder<I> {
105    setup: Option<Setup<I>>,
106    actions: Vec<ActionBox<I>>,
107    env: Environment<I>,
108}
109
110impl<I: 'static> TestBuilder<I> {
111    /// Create a new test builder
112    pub fn new() -> Self {
113        Self { setup: None, actions: Vec::new(), env: Default::default() }
114    }
115
116    /// Set the test setup
117    pub fn with_setup(mut self, setup: Setup<I>) -> Self {
118        self.setup = Some(setup);
119        self
120    }
121
122    /// Add an action to the test
123    pub fn with_action<A>(mut self, action: A) -> Self
124    where
125        A: Action<I>,
126    {
127        self.actions.push(ActionBox::<I>::new(action));
128        self
129    }
130
131    /// Add multiple actions to the test
132    pub fn with_actions<II, A>(mut self, actions: II) -> Self
133    where
134        II: IntoIterator<Item = A>,
135        A: Action<I>,
136    {
137        self.actions.extend(actions.into_iter().map(ActionBox::new));
138        self
139    }
140
141    /// Run the test scenario
142    pub async fn run<N>(mut self) -> Result<()>
143    where
144        N: NodeBuilderHelper,
145        LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
146            <<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
147        >,
148    {
149        let mut setup = self.setup.take();
150
151        if let Some(ref mut s) = setup {
152            s.apply::<N>(&mut self.env).await?;
153        }
154
155        let actions = std::mem::take(&mut self.actions);
156
157        for action in actions {
158            action.execute(&mut self.env).await?;
159        }
160
161        // explicitly drop the setup to shutdown the nodes
162        // after all actions have completed
163        drop(setup);
164
165        Ok(())
166    }
167}