Skip to content

Commit b5c01d6

Browse files
authored
refactor(e2e): split actions.rs into submodule (#16609)
1 parent 2726b79 commit b5c01d6

File tree

4 files changed

+621
-561
lines changed

4 files changed

+621
-561
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//! Fork creation actions for the e2e testing framework.
2+
3+
use crate::testsuite::{
4+
actions::{produce_blocks::ProduceBlocks, Sequence},
5+
Action, Environment, LatestBlockInfo,
6+
};
7+
use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes};
8+
use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction};
9+
use eyre::Result;
10+
use futures_util::future::BoxFuture;
11+
use reth_node_api::{EngineTypes, PayloadTypes};
12+
use reth_rpc_api::clients::EthApiClient;
13+
use std::marker::PhantomData;
14+
use tracing::debug;
15+
16+
/// Action to create a fork from a specified block number and produce blocks on top
17+
#[derive(Debug)]
18+
pub struct CreateFork<Engine> {
19+
/// Block number to use as the base of the fork
20+
pub fork_base_block: u64,
21+
/// Number of blocks to produce on top of the fork base
22+
pub num_blocks: u64,
23+
/// Tracks engine type
24+
_phantom: PhantomData<Engine>,
25+
}
26+
27+
impl<Engine> CreateFork<Engine> {
28+
/// Create a new `CreateFork` action
29+
pub fn new(fork_base_block: u64, num_blocks: u64) -> Self {
30+
Self { fork_base_block, num_blocks, _phantom: Default::default() }
31+
}
32+
}
33+
34+
impl<Engine> Action<Engine> for CreateFork<Engine>
35+
where
36+
Engine: EngineTypes + PayloadTypes,
37+
Engine::PayloadAttributes: From<PayloadAttributes> + Clone,
38+
Engine::ExecutionPayloadEnvelopeV3:
39+
Into<alloy_rpc_types_engine::payload::ExecutionPayloadEnvelopeV3>,
40+
{
41+
fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
42+
Box::pin(async move {
43+
let mut sequence = Sequence::new(vec![
44+
Box::new(SetForkBase::new(self.fork_base_block)),
45+
Box::new(ProduceBlocks::new(self.num_blocks)),
46+
Box::new(ValidateFork::new(self.fork_base_block)),
47+
]);
48+
49+
sequence.execute(env).await
50+
})
51+
}
52+
}
53+
54+
/// Sub-action to set the fork base block in the environment
55+
#[derive(Debug)]
56+
pub struct SetForkBase {
57+
/// Block number to use as the base of the fork
58+
pub fork_base_block: u64,
59+
}
60+
61+
impl SetForkBase {
62+
/// Create a new `SetForkBase` action
63+
pub const fn new(fork_base_block: u64) -> Self {
64+
Self { fork_base_block }
65+
}
66+
}
67+
68+
impl<Engine> Action<Engine> for SetForkBase
69+
where
70+
Engine: EngineTypes,
71+
{
72+
fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
73+
Box::pin(async move {
74+
if env.node_clients.is_empty() {
75+
return Err(eyre::eyre!("No node clients available"));
76+
}
77+
78+
// get the block at the fork base number to establish the fork point
79+
let rpc_client = &env.node_clients[0].rpc;
80+
let fork_base_block =
81+
EthApiClient::<Transaction, Block, Receipt, Header>::block_by_number(
82+
rpc_client,
83+
alloy_eips::BlockNumberOrTag::Number(self.fork_base_block),
84+
false,
85+
)
86+
.await?
87+
.ok_or_else(|| eyre::eyre!("Fork base block {} not found", self.fork_base_block))?;
88+
89+
// update environment to point to the fork base block
90+
env.latest_block_info = Some(LatestBlockInfo {
91+
hash: fork_base_block.header.hash,
92+
number: fork_base_block.header.number,
93+
});
94+
95+
env.latest_header_time = fork_base_block.header.timestamp;
96+
97+
// update fork choice state to the fork base
98+
env.latest_fork_choice_state = ForkchoiceState {
99+
head_block_hash: fork_base_block.header.hash,
100+
safe_block_hash: fork_base_block.header.hash,
101+
finalized_block_hash: fork_base_block.header.hash,
102+
};
103+
104+
debug!(
105+
"Set fork base to block {} (hash: {})",
106+
self.fork_base_block, fork_base_block.header.hash
107+
);
108+
109+
Ok(())
110+
})
111+
}
112+
}
113+
114+
/// Sub-action to validate that a fork was created correctly
115+
#[derive(Debug)]
116+
pub struct ValidateFork {
117+
/// Number of the fork base block (stored here since we need it for validation)
118+
pub fork_base_number: u64,
119+
}
120+
121+
impl ValidateFork {
122+
/// Create a new `ValidateFork` action
123+
pub const fn new(fork_base_number: u64) -> Self {
124+
Self { fork_base_number }
125+
}
126+
}
127+
128+
impl<Engine> Action<Engine> for ValidateFork
129+
where
130+
Engine: EngineTypes,
131+
{
132+
fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
133+
Box::pin(async move {
134+
let current_block_info = env
135+
.latest_block_info
136+
.as_ref()
137+
.ok_or_else(|| eyre::eyre!("No current block information available"))?;
138+
139+
// verify that the current tip is at or ahead of the fork base
140+
if current_block_info.number < self.fork_base_number {
141+
return Err(eyre::eyre!(
142+
"Fork validation failed: current block number {} is behind fork base {}",
143+
current_block_info.number,
144+
self.fork_base_number
145+
));
146+
}
147+
148+
// get the fork base hash from the environment's fork choice state
149+
// we assume the fork choice state was set correctly by SetForkBase
150+
let fork_base_hash = env.latest_fork_choice_state.finalized_block_hash;
151+
152+
// trace back from current tip to verify it's a descendant of the fork base
153+
let rpc_client = &env.node_clients[0].rpc;
154+
let mut current_hash = current_block_info.hash;
155+
let mut current_number = current_block_info.number;
156+
157+
// walk backwards through the chain until we reach the fork base
158+
while current_number > self.fork_base_number {
159+
let block = EthApiClient::<Transaction, Block, Receipt, Header>::block_by_hash(
160+
rpc_client,
161+
current_hash,
162+
false,
163+
)
164+
.await?
165+
.ok_or_else(|| {
166+
eyre::eyre!("Block with hash {} not found during fork validation", current_hash)
167+
})?;
168+
169+
current_hash = block.header.parent_hash;
170+
current_number = block.header.number.saturating_sub(1);
171+
}
172+
173+
// verify we reached the expected fork base
174+
if current_hash != fork_base_hash {
175+
return Err(eyre::eyre!(
176+
"Fork validation failed: expected fork base hash {}, but found {} at block {}",
177+
fork_base_hash,
178+
current_hash,
179+
current_number
180+
));
181+
}
182+
183+
debug!(
184+
"Fork validation successful: tip block {} is descendant of fork base {} ({})",
185+
current_block_info.number, self.fork_base_number, fork_base_hash
186+
);
187+
188+
Ok(())
189+
})
190+
}
191+
}

0 commit comments

Comments
 (0)