Skip to main content

reth_e2e_test_utils/testsuite/actions/
reorg.rs

1//! Reorg actions for the e2e testing framework.
2
3use crate::testsuite::{
4    actions::{produce_blocks::broadcast_forkchoice, Action},
5    BlockInfo, Environment,
6};
7use alloy_primitives::B256;
8use alloy_rpc_types_engine::ForkchoiceState;
9use eyre::Result;
10use futures_util::future::BoxFuture;
11use reth_node_api::EngineTypes;
12use std::marker::PhantomData;
13use tracing::debug;
14
15/// Target for reorg operation
16#[derive(Debug, Clone)]
17pub enum ReorgTarget {
18    /// Direct block hash
19    Hash(B256),
20    /// Tagged block reference
21    Tag(String),
22}
23
24/// Action that performs a reorg by setting a new head block as canonical.
25///
26/// Points the active node's environment at the target block and broadcasts a forkchoice update
27/// with the target as head to all clients. The update leaves the finalized block unchanged, so a
28/// target below it is rejected as a too deep reorg.
29#[derive(Debug)]
30pub struct ReorgTo<Engine> {
31    /// Target for the reorg operation
32    pub target: ReorgTarget,
33    /// Tracks engine type
34    _phantom: PhantomData<Engine>,
35}
36
37impl<Engine> ReorgTo<Engine> {
38    /// Create a new `ReorgTo` action with a direct block hash
39    pub const fn new(target_hash: B256) -> Self {
40        Self { target: ReorgTarget::Hash(target_hash), _phantom: PhantomData }
41    }
42
43    /// Create a new `ReorgTo` action with a tagged block reference
44    pub fn new_from_tag(tag: impl Into<String>) -> Self {
45        Self { target: ReorgTarget::Tag(tag.into()), _phantom: PhantomData }
46    }
47}
48
49impl<Engine> Action<Engine> for ReorgTo<Engine>
50where
51    Engine: EngineTypes,
52{
53    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
54        Box::pin(async move {
55            // resolve the target block info from either direct hash or tag
56            let target_block_info = match &self.target {
57                ReorgTarget::Hash(_hash) => {
58                    return Err(eyre::eyre!(
59                        "Direct hash reorgs are not supported. Use CaptureBlock to tag the target block first, then use ReorgTo::new_from_tag()"
60                    ));
61                }
62                ReorgTarget::Tag(tag) => {
63                    let (block_info, _node_idx) =
64                        env.block_registry.get(tag).copied().ok_or_else(|| {
65                            eyre::eyre!("Block tag '{}' not found in registry", tag)
66                        })?;
67                    block_info
68                }
69            };
70
71            let mut set_target = SetReorgTarget::new(target_block_info);
72            set_target.execute(env).await?;
73
74            // Target the reorg block itself rather than the latest built payload, which can be a
75            // descendant of the target or belong to another fork.
76            broadcast_forkchoice(env, target_block_info.hash).await
77        })
78    }
79}
80
81/// Sub-action to set the reorg target block in the environment
82#[derive(Debug)]
83pub struct SetReorgTarget {
84    /// Complete block info for the reorg target
85    pub target_block_info: BlockInfo,
86}
87
88impl SetReorgTarget {
89    /// Create a new `SetReorgTarget` action
90    pub const fn new(target_block_info: BlockInfo) -> Self {
91        Self { target_block_info }
92    }
93}
94
95impl<Engine> Action<Engine> for SetReorgTarget
96where
97    Engine: EngineTypes,
98{
99    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
100        Box::pin(async move {
101            let block_info = self.target_block_info;
102
103            debug!(
104                "Setting reorg target to block {} (hash: {})",
105                block_info.number, block_info.hash
106            );
107
108            // update active node state to point to the target block
109            let active_node_state = env.active_node_state_mut()?;
110            active_node_state.current_block_info = Some(block_info);
111            active_node_state.latest_header_time = block_info.timestamp;
112
113            // update fork choice state to make the target block canonical
114            active_node_state.latest_fork_choice_state =
115                ForkchoiceState::same_hash(block_info.hash);
116
117            debug!("Set reorg target to block {}", block_info.hash);
118            Ok(())
119        })
120    }
121}