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