reth_e2e_test_utils/testsuite/actions/
fork.rs1use 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, Header, Receipt, Transaction, TransactionRequest};
9use eyre::Result;
10use futures_util::future::BoxFuture;
11use reth_ethereum_primitives::TransactionSigned;
12use reth_node_api::{EngineTypes, PayloadTypes};
13use reth_rpc_api::clients::EthApiClient;
14use std::marker::PhantomData;
15use tracing::debug;
16
17#[derive(Debug, Clone)]
19pub enum ForkBase {
20 Number(u64),
22 Tag(String),
24}
25
26#[derive(Debug)]
28pub struct CreateFork<Engine> {
29 pub fork_base: ForkBase,
31 pub num_blocks: u64,
33 _phantom: PhantomData<Engine>,
35}
36
37impl<Engine> CreateFork<Engine> {
38 pub fn new(fork_base_block: u64, num_blocks: u64) -> Self {
40 Self {
41 fork_base: ForkBase::Number(fork_base_block),
42 num_blocks,
43 _phantom: Default::default(),
44 }
45 }
46
47 pub fn new_from_tag(tag: impl Into<String>, num_blocks: u64) -> Self {
49 Self { fork_base: ForkBase::Tag(tag.into()), num_blocks, _phantom: Default::default() }
50 }
51}
52
53impl<Engine> Action<Engine> for CreateFork<Engine>
54where
55 Engine: EngineTypes + PayloadTypes,
56 Engine::PayloadAttributes: From<PayloadAttributes> + Clone,
57 Engine::ExecutionPayloadEnvelopeV3:
58 Into<alloy_rpc_types_engine::payload::ExecutionPayloadEnvelopeV3>,
59{
60 fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
61 Box::pin(async move {
62 match &self.fork_base {
64 ForkBase::Number(block_number) => {
65 env.active_node_state_mut()?.current_fork_base = Some(*block_number);
67
68 let mut sequence = Sequence::new(vec![
69 Box::new(SetForkBase::new(*block_number)),
70 Box::new(ProduceBlocks::new(self.num_blocks)),
71 ]);
72 sequence.execute(env).await
73 }
74 ForkBase::Tag(tag) => {
75 let (block_info, _node_idx) =
76 env.block_registry.get(tag).copied().ok_or_else(|| {
77 eyre::eyre!("Block tag '{}' not found in registry", tag)
78 })?;
79
80 env.active_node_state_mut()?.current_fork_base = Some(block_info.number);
82
83 let mut sequence = Sequence::new(vec![
84 Box::new(SetForkBaseFromBlockInfo::new(block_info)),
85 Box::new(ProduceBlocks::new(self.num_blocks)),
86 ]);
87 sequence.execute(env).await
88 }
89 }
90 })
91 }
92}
93
94#[derive(Debug)]
96pub struct SetForkBase {
97 pub fork_base_block: u64,
99}
100
101#[derive(Debug)]
103pub struct SetForkBaseFromBlockInfo {
104 pub fork_base_info: BlockInfo,
106}
107
108impl SetForkBase {
109 pub const fn new(fork_base_block: u64) -> Self {
111 Self { fork_base_block }
112 }
113}
114
115impl SetForkBaseFromBlockInfo {
116 pub const fn new(fork_base_info: BlockInfo) -> Self {
118 Self { fork_base_info }
119 }
120}
121
122impl<Engine> Action<Engine> for SetForkBase
123where
124 Engine: EngineTypes,
125{
126 fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
127 Box::pin(async move {
128 if env.node_clients.is_empty() {
129 return Err(eyre::eyre!("No node clients available"));
130 }
131
132 let rpc_client = &env.node_clients[0].rpc;
134 let fork_base_block = EthApiClient::<
135 TransactionRequest,
136 Transaction,
137 Block,
138 Receipt,
139 Header,
140 TransactionSigned,
141 >::block_by_number(
142 rpc_client,
143 alloy_eips::BlockNumberOrTag::Number(self.fork_base_block),
144 false,
145 )
146 .await?
147 .ok_or_else(|| eyre::eyre!("Fork base block {} not found", self.fork_base_block))?;
148
149 let active_node_state = env.active_node_state_mut()?;
151 active_node_state.current_block_info = Some(BlockInfo {
152 hash: fork_base_block.header.hash,
153 number: fork_base_block.header.number,
154 timestamp: fork_base_block.header.timestamp,
155 });
156
157 active_node_state.latest_header_time = fork_base_block.header.timestamp;
158
159 active_node_state.latest_fork_choice_state =
161 ForkchoiceState::same_hash(fork_base_block.header.hash);
162
163 debug!(
164 "Set fork base to block {} (hash: {})",
165 self.fork_base_block, fork_base_block.header.hash
166 );
167
168 Ok(())
169 })
170 }
171}
172
173impl<Engine> Action<Engine> for SetForkBaseFromBlockInfo
174where
175 Engine: EngineTypes,
176{
177 fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
178 Box::pin(async move {
179 let block_info = self.fork_base_info;
180
181 debug!(
182 "Set fork base from block info: block {} (hash: {})",
183 block_info.number, block_info.hash
184 );
185
186 let active_node_state = env.active_node_state_mut()?;
188 active_node_state.current_block_info = Some(block_info);
189 active_node_state.latest_header_time = block_info.timestamp;
190
191 active_node_state.latest_fork_choice_state =
193 ForkchoiceState::same_hash(block_info.hash);
194
195 debug!("Set fork base to block {} (hash: {})", block_info.number, block_info.hash);
196
197 Ok(())
198 })
199 }
200}
201
202#[derive(Debug)]
204pub struct ValidateFork {
205 pub fork_base_number: u64,
207}
208
209impl ValidateFork {
210 pub const fn new(fork_base_number: u64) -> Self {
212 Self { fork_base_number }
213 }
214}
215
216impl<Engine> Action<Engine> for ValidateFork
217where
218 Engine: EngineTypes,
219{
220 fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
221 Box::pin(async move {
222 let current_block_info = env
223 .current_block_info()
224 .ok_or_else(|| eyre::eyre!("No current block information available"))?;
225
226 if current_block_info.number < self.fork_base_number {
228 return Err(eyre::eyre!(
229 "Fork validation failed: current block number {} is behind fork base {}",
230 current_block_info.number,
231 self.fork_base_number
232 ));
233 }
234
235 let fork_base_hash =
238 env.active_node_state()?.latest_fork_choice_state.finalized_block_hash;
239
240 let rpc_client = &env.node_clients[0].rpc;
242 let mut current_hash = current_block_info.hash;
243 let mut current_number = current_block_info.number;
244
245 while current_number > self.fork_base_number {
247 let block = EthApiClient::<
248 TransactionRequest,
249 Transaction,
250 Block,
251 Receipt,
252 Header,
253 TransactionSigned,
254 >::block_by_hash(rpc_client, current_hash, false)
255 .await?
256 .ok_or_else(|| {
257 eyre::eyre!("Block with hash {} not found during fork validation", current_hash)
258 })?;
259
260 current_hash = block.header.parent_hash;
261 current_number = block.header.number.saturating_sub(1);
262 }
263
264 if current_hash != fork_base_hash {
266 return Err(eyre::eyre!(
267 "Fork validation failed: expected fork base hash {}, but found {} at block {}",
268 fork_base_hash,
269 current_hash,
270 current_number
271 ));
272 }
273
274 debug!(
275 "Fork validation successful: tip block {} is descendant of fork base {} ({})",
276 current_block_info.number, self.fork_base_number, fork_base_hash
277 );
278
279 Ok(())
280 })
281 }
282}