Skip to main content

reth_e2e_test_utils/testsuite/actions/
fork.rs

1//! Fork creation actions for the e2e testing framework.
2
3use crate::testsuite::{
4    actions::{produce_blocks::ProduceBlocks, Sequence},
5    Action, BlockInfo, Environment,
6};
7use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes};
8use alloy_rpc_types_eth::Block;
9use eyre::Result;
10use futures_util::future::BoxFuture;
11use reth_node_api::EngineTypes;
12use std::marker::PhantomData;
13use tracing::debug;
14
15/// Fork base target for fork creation
16#[derive(Debug, Clone)]
17pub enum ForkBase {
18    /// Block number
19    Number(u64),
20    /// Tagged block reference
21    Tag(String),
22}
23
24/// Action to create a fork from a specified block and produce blocks on top
25#[derive(Debug)]
26pub struct CreateFork<Engine> {
27    /// Fork base specification (either block number or tag)
28    pub fork_base: ForkBase,
29    /// Number of blocks to produce on top of the fork base
30    pub num_blocks: u64,
31    /// Tracks engine type
32    _phantom: PhantomData<Engine>,
33}
34
35impl<Engine> CreateFork<Engine> {
36    /// Create a new `CreateFork` action from a block number
37    pub fn new(fork_base_block: u64, num_blocks: u64) -> Self {
38        Self {
39            fork_base: ForkBase::Number(fork_base_block),
40            num_blocks,
41            _phantom: Default::default(),
42        }
43    }
44
45    /// Create a new `CreateFork` action from a tagged block
46    pub fn new_from_tag(tag: impl Into<String>, num_blocks: u64) -> Self {
47        Self { fork_base: ForkBase::Tag(tag.into()), num_blocks, _phantom: Default::default() }
48    }
49}
50
51impl<Engine> Action<Engine> for CreateFork<Engine>
52where
53    Engine: EngineTypes,
54    Engine::PayloadAttributes: From<PayloadAttributes> + Clone,
55    Engine::ExecutionPayloadEnvelopeV3:
56        Into<alloy_rpc_types_engine::payload::ExecutionPayloadEnvelopeV3>,
57{
58    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
59        Box::pin(async move {
60            // resolve the fork base and execute the appropriate sequence
61            match &self.fork_base {
62                ForkBase::Number(block_number) => {
63                    // store the fork base for later validation on the active node
64                    env.active_node_state_mut()?.current_fork_base = Some(*block_number);
65
66                    let mut sequence = Sequence::new(vec![
67                        Box::new(SetForkBase::new(*block_number)),
68                        Box::new(ProduceBlocks::new(self.num_blocks)),
69                    ]);
70                    sequence.execute(env).await
71                }
72                ForkBase::Tag(tag) => {
73                    let (block_info, _node_idx) =
74                        env.block_registry.get(tag).copied().ok_or_else(|| {
75                            eyre::eyre!("Block tag '{}' not found in registry", tag)
76                        })?;
77
78                    // store the fork base for later validation on the active node
79                    env.active_node_state_mut()?.current_fork_base = Some(block_info.number);
80
81                    let mut sequence = Sequence::new(vec![
82                        Box::new(SetForkBaseFromBlockInfo::new(block_info)),
83                        Box::new(ProduceBlocks::new(self.num_blocks)),
84                    ]);
85                    sequence.execute(env).await
86                }
87            }
88        })
89    }
90}
91
92/// Sub-action to set the fork base block in the environment
93#[derive(Debug)]
94pub struct SetForkBase {
95    /// Block number to use as the base of the fork
96    pub fork_base_block: u64,
97}
98
99/// Sub-action to set the fork base block from existing block info
100#[derive(Debug)]
101pub struct SetForkBaseFromBlockInfo {
102    /// Complete block info to use as the base of the fork
103    pub fork_base_info: BlockInfo,
104}
105
106impl SetForkBase {
107    /// Create a new `SetForkBase` action
108    pub const fn new(fork_base_block: u64) -> Self {
109        Self { fork_base_block }
110    }
111}
112
113impl SetForkBaseFromBlockInfo {
114    /// Create a new `SetForkBaseFromBlockInfo` action
115    pub const fn new(fork_base_info: BlockInfo) -> Self {
116        Self { fork_base_info }
117    }
118}
119
120impl<Engine> Action<Engine> for SetForkBase
121where
122    Engine: EngineTypes,
123{
124    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
125        Box::pin(async move {
126            if env.node_clients.is_empty() {
127                return Err(eyre::eyre!("No node clients available"));
128            }
129
130            // get the block at the fork base number to establish the fork point
131            let rpc_client = &env.node_clients[0].rpc;
132            let fork_base_block = rpc_client
133                .request::<_, Option<Block>>(
134                    "eth_getBlockByNumber",
135                    (alloy_eips::BlockNumberOrTag::Number(self.fork_base_block), false),
136                )
137                .await?
138                .ok_or_else(|| eyre::eyre!("Fork base block {} not found", self.fork_base_block))?;
139
140            // update active node state to point to the fork base block
141            let active_node_state = env.active_node_state_mut()?;
142            active_node_state.current_block_info = Some(BlockInfo {
143                hash: fork_base_block.header.hash,
144                number: fork_base_block.header.number,
145                timestamp: fork_base_block.header.timestamp,
146            });
147
148            active_node_state.latest_header_time = fork_base_block.header.timestamp;
149
150            // update fork choice state to the fork base
151            active_node_state.latest_fork_choice_state =
152                ForkchoiceState::same_hash(fork_base_block.header.hash);
153
154            debug!(
155                "Set fork base to block {} (hash: {})",
156                self.fork_base_block, fork_base_block.header.hash
157            );
158
159            Ok(())
160        })
161    }
162}
163
164impl<Engine> Action<Engine> for SetForkBaseFromBlockInfo
165where
166    Engine: EngineTypes,
167{
168    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
169        Box::pin(async move {
170            let block_info = self.fork_base_info;
171
172            debug!(
173                "Set fork base from block info: block {} (hash: {})",
174                block_info.number, block_info.hash
175            );
176
177            // update active node state to point to the fork base block
178            let active_node_state = env.active_node_state_mut()?;
179            active_node_state.current_block_info = Some(block_info);
180            active_node_state.latest_header_time = block_info.timestamp;
181
182            // update fork choice state to the fork base
183            active_node_state.latest_fork_choice_state =
184                ForkchoiceState::same_hash(block_info.hash);
185
186            debug!("Set fork base to block {} (hash: {})", block_info.number, block_info.hash);
187
188            Ok(())
189        })
190    }
191}
192
193/// Sub-action to validate that a fork was created correctly
194#[derive(Debug)]
195pub struct ValidateFork {
196    /// Number of the fork base block (stored here since we need it for validation)
197    pub fork_base_number: u64,
198}
199
200impl ValidateFork {
201    /// Create a new `ValidateFork` action
202    pub const fn new(fork_base_number: u64) -> Self {
203        Self { fork_base_number }
204    }
205}
206
207impl<Engine> Action<Engine> for ValidateFork
208where
209    Engine: EngineTypes,
210{
211    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
212        Box::pin(async move {
213            let current_block_info = env
214                .current_block_info()
215                .ok_or_else(|| eyre::eyre!("No current block information available"))?;
216
217            // verify that the current tip is at or ahead of the fork base
218            if current_block_info.number < self.fork_base_number {
219                return Err(eyre::eyre!(
220                    "Fork validation failed: current block number {} is behind fork base {}",
221                    current_block_info.number,
222                    self.fork_base_number
223                ));
224            }
225
226            // get the fork base hash from the environment's fork choice state
227            // we assume the fork choice state was set correctly by SetForkBase
228            let fork_base_hash =
229                env.active_node_state()?.latest_fork_choice_state.finalized_block_hash;
230
231            // trace back from current tip to verify it's a descendant of the fork base
232            let rpc_client = &env.node_clients[0].rpc;
233            let mut current_hash = current_block_info.hash;
234            let mut current_number = current_block_info.number;
235
236            // walk backwards through the chain until we reach the fork base
237            while current_number > self.fork_base_number {
238                let block = rpc_client
239                    .request::<_, Option<Block>>("eth_getBlockByHash", (current_hash, false))
240                    .await?
241                    .ok_or_else(|| {
242                        eyre::eyre!(
243                            "Block with hash {} not found during fork validation",
244                            current_hash
245                        )
246                    })?;
247
248                current_hash = block.header.parent_hash;
249                current_number = block.header.number.saturating_sub(1);
250            }
251
252            // verify we reached the expected fork base
253            if current_hash != fork_base_hash {
254                return Err(eyre::eyre!(
255                    "Fork validation failed: expected fork base hash {}, but found {} at block {}",
256                    fork_base_hash,
257                    current_hash,
258                    current_number
259                ));
260            }
261
262            debug!(
263                "Fork validation successful: tip block {} is descendant of fork base {} ({})",
264                current_block_info.number, self.fork_base_number, fork_base_hash
265            );
266
267            Ok(())
268        })
269    }
270}