reth_e2e_test_utils/testsuite/actions/
mod.rs

1//! Actions that can be performed in tests.
2
3use crate::testsuite::Environment;
4use alloy_rpc_types_engine::{ForkchoiceState, ForkchoiceUpdated, PayloadStatusEnum};
5use eyre::Result;
6use futures_util::future::BoxFuture;
7use reth_node_api::EngineTypes;
8use reth_rpc_api::clients::EngineApiClient;
9use std::future::Future;
10use tracing::debug;
11
12pub mod custom_fcu;
13pub mod engine_api;
14pub mod fork;
15pub mod node_ops;
16pub mod produce_blocks;
17pub mod reorg;
18
19pub use custom_fcu::{BlockReference, FinalizeBlock, SendForkchoiceUpdate};
20pub use engine_api::{ExpectedPayloadStatus, SendNewPayload, SendNewPayloads};
21pub use fork::{CreateFork, ForkBase, SetForkBase, SetForkBaseFromBlockInfo, ValidateFork};
22pub use node_ops::{
23    AssertChainTip, CaptureBlockOnNode, CompareNodeChainTips, SelectActiveNode, ValidateBlockTag,
24    WaitForSync,
25};
26pub use produce_blocks::{
27    AssertMineBlock, BroadcastLatestForkchoice, BroadcastNextNewPayload, CheckPayloadAccepted,
28    ExpectFcuStatus, GenerateNextPayload, GeneratePayloadAttributes, PickNextBlockProducer,
29    ProduceBlocks, ProduceBlocksLocally, ProduceInvalidBlocks, TestFcuToTag, UpdateBlockInfo,
30    UpdateBlockInfoToLatestPayload, ValidateCanonicalTag,
31};
32pub use reorg::{ReorgTarget, ReorgTo, SetReorgTarget};
33
34/// An action that can be performed on an instance.
35///
36/// Actions execute operations and potentially make assertions in a single step.
37/// The action name indicates what it does (e.g., `AssertMineBlock` would both
38/// mine a block and assert it worked).
39pub trait Action<I>: Send + 'static
40where
41    I: EngineTypes,
42{
43    /// Executes the action
44    fn execute<'a>(&'a mut self, env: &'a mut Environment<I>) -> BoxFuture<'a, Result<()>>;
45}
46
47/// Simplified action container for storage in tests
48#[expect(missing_debug_implementations)]
49pub struct ActionBox<I>(Box<dyn Action<I>>);
50
51impl<I> ActionBox<I>
52where
53    I: EngineTypes + 'static,
54{
55    /// Constructor for [`ActionBox`].
56    pub fn new<A: Action<I>>(action: A) -> Self {
57        Self(Box::new(action))
58    }
59
60    /// Executes an [`ActionBox`] with the given [`Environment`] reference.
61    pub async fn execute(mut self, env: &mut Environment<I>) -> Result<()> {
62        self.0.execute(env).await
63    }
64}
65
66/// Implementation of `Action` for any function/closure that takes an Environment
67/// reference and returns a Future resolving to Result<()>.
68///
69/// This allows using closures directly as actions with `.with_action(async move |env| {...})`.
70impl<I, F, Fut> Action<I> for F
71where
72    I: EngineTypes,
73    F: FnMut(&Environment<I>) -> Fut + Send + 'static,
74    Fut: Future<Output = Result<()>> + Send + 'static,
75{
76    fn execute<'a>(&'a mut self, env: &'a mut Environment<I>) -> BoxFuture<'a, Result<()>> {
77        Box::pin(self(env))
78    }
79}
80
81/// Run a sequence of actions in series.
82#[expect(missing_debug_implementations)]
83pub struct Sequence<I> {
84    /// Actions to execute in sequence
85    pub actions: Vec<Box<dyn Action<I>>>,
86}
87
88impl<I> Sequence<I> {
89    /// Create a new sequence of actions
90    pub fn new(actions: Vec<Box<dyn Action<I>>>) -> Self {
91        Self { actions }
92    }
93}
94
95impl<I> Action<I> for Sequence<I>
96where
97    I: EngineTypes + Sync + Send + 'static,
98{
99    fn execute<'a>(&'a mut self, env: &'a mut Environment<I>) -> BoxFuture<'a, Result<()>> {
100        Box::pin(async move {
101            // Execute each action in sequence
102            for action in &mut self.actions {
103                action.execute(env).await?;
104            }
105
106            Ok(())
107        })
108    }
109}
110
111/// Action that makes the current latest block canonical by broadcasting a forkchoice update
112#[derive(Debug, Default)]
113pub struct MakeCanonical {
114    /// If true, only send to the active node. If false, broadcast to all nodes.
115    active_node_only: bool,
116}
117
118impl MakeCanonical {
119    /// Create a new `MakeCanonical` action
120    pub const fn new() -> Self {
121        Self { active_node_only: false }
122    }
123
124    /// Create a new `MakeCanonical` action that only applies to the active node
125    pub const fn with_active_node() -> Self {
126        Self { active_node_only: true }
127    }
128}
129
130impl<Engine> Action<Engine> for MakeCanonical
131where
132    Engine: EngineTypes + reth_node_api::PayloadTypes,
133    Engine::PayloadAttributes: From<alloy_rpc_types_engine::PayloadAttributes> + Clone,
134    Engine::ExecutionPayloadEnvelopeV3:
135        Into<alloy_rpc_types_engine::payload::ExecutionPayloadEnvelopeV3>,
136{
137    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
138        Box::pin(async move {
139            if self.active_node_only {
140                // Only update the active node
141                let latest_block = env
142                    .current_block_info()
143                    .ok_or_else(|| eyre::eyre!("No latest block information available"))?;
144
145                let fork_choice_state = ForkchoiceState {
146                    head_block_hash: latest_block.hash,
147                    safe_block_hash: latest_block.hash,
148                    finalized_block_hash: latest_block.hash,
149                };
150
151                let active_idx = env.active_node_idx;
152                let engine = env.node_clients[active_idx].engine.http_client();
153
154                let fcu_response = EngineApiClient::<Engine>::fork_choice_updated_v3(
155                    &engine,
156                    fork_choice_state,
157                    None,
158                )
159                .await?;
160
161                debug!(
162                    "Active node {}: Forkchoice update status: {:?}",
163                    active_idx, fcu_response.payload_status.status
164                );
165
166                validate_fcu_response(&fcu_response, &format!("Active node {active_idx}"))?;
167
168                Ok(())
169            } else {
170                // Original broadcast behavior
171                let mut actions: Vec<Box<dyn Action<Engine>>> = vec![
172                    Box::new(BroadcastLatestForkchoice::default()),
173                    Box::new(UpdateBlockInfo::default()),
174                ];
175
176                // if we're on a fork, validate it now that it's canonical
177                if let Ok(active_state) = env.active_node_state() {
178                    if let Some(fork_base) = active_state.current_fork_base {
179                        debug!(
180                            "MakeCanonical: Adding fork validation from base block {}",
181                            fork_base
182                        );
183                        actions.push(Box::new(ValidateFork::new(fork_base)));
184                        // clear the fork base since we're now canonical
185                        env.active_node_state_mut()?.current_fork_base = None;
186                    }
187                }
188
189                let mut sequence = Sequence::new(actions);
190                sequence.execute(env).await
191            }
192        })
193    }
194}
195
196/// Action that captures the current block and tags it with a name for later reference
197#[derive(Debug)]
198pub struct CaptureBlock {
199    /// Tag name to associate with the current block
200    pub tag: String,
201}
202
203impl CaptureBlock {
204    /// Create a new `CaptureBlock` action
205    pub fn new(tag: impl Into<String>) -> Self {
206        Self { tag: tag.into() }
207    }
208}
209
210impl<Engine> Action<Engine> for CaptureBlock
211where
212    Engine: EngineTypes,
213{
214    fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
215        Box::pin(async move {
216            let current_block = env
217                .current_block_info()
218                .ok_or_else(|| eyre::eyre!("No current block information available"))?;
219
220            env.block_registry.insert(self.tag.clone(), (current_block, env.active_node_idx));
221
222            debug!(
223                "Captured block {} (hash: {}) from active node {} with tag '{}'",
224                current_block.number, current_block.hash, env.active_node_idx, self.tag
225            );
226
227            Ok(())
228        })
229    }
230}
231
232/// Validates a forkchoice update response and returns an error if invalid
233pub fn validate_fcu_response(response: &ForkchoiceUpdated, context: &str) -> Result<()> {
234    match &response.payload_status.status {
235        PayloadStatusEnum::Valid => {
236            debug!("{}: FCU accepted as valid", context);
237            Ok(())
238        }
239        PayloadStatusEnum::Invalid { validation_error } => {
240            Err(eyre::eyre!("{}: FCU rejected as invalid: {:?}", context, validation_error))
241        }
242        PayloadStatusEnum::Syncing => {
243            debug!("{}: FCU accepted, node is syncing", context);
244            Ok(())
245        }
246        PayloadStatusEnum::Accepted => {
247            debug!("{}: FCU accepted for processing", context);
248            Ok(())
249        }
250    }
251}
252
253/// Expects that the `ForkchoiceUpdated` response status is VALID.
254pub fn expect_fcu_valid(response: &ForkchoiceUpdated, context: &str) -> Result<()> {
255    match &response.payload_status.status {
256        PayloadStatusEnum::Valid => {
257            debug!("{}: FCU status is VALID as expected.", context);
258            Ok(())
259        }
260        other_status => {
261            Err(eyre::eyre!("{}: Expected FCU status VALID, but got {:?}", context, other_status))
262        }
263    }
264}
265
266/// Expects that the `ForkchoiceUpdated` response status is INVALID.
267pub fn expect_fcu_invalid(response: &ForkchoiceUpdated, context: &str) -> Result<()> {
268    match &response.payload_status.status {
269        PayloadStatusEnum::Invalid { validation_error } => {
270            debug!("{}: FCU status is INVALID as expected: {:?}", context, validation_error);
271            Ok(())
272        }
273        other_status => {
274            Err(eyre::eyre!("{}: Expected FCU status INVALID, but got {:?}", context, other_status))
275        }
276    }
277}
278
279/// Expects that the `ForkchoiceUpdated` response status is either SYNCING or ACCEPTED.
280pub fn expect_fcu_syncing_or_accepted(response: &ForkchoiceUpdated, context: &str) -> Result<()> {
281    match &response.payload_status.status {
282        PayloadStatusEnum::Syncing => {
283            debug!("{}: FCU status is SYNCING as expected (SYNCING or ACCEPTED).", context);
284            Ok(())
285        }
286        PayloadStatusEnum::Accepted => {
287            debug!("{}: FCU status is ACCEPTED as expected (SYNCING or ACCEPTED).", context);
288            Ok(())
289        }
290        other_status => Err(eyre::eyre!(
291            "{}: Expected FCU status SYNCING or ACCEPTED, but got {:?}",
292            context,
293            other_status
294        )),
295    }
296}
297
298/// Expects that the `ForkchoiceUpdated` response status is not SYNCING and not ACCEPTED.
299pub fn expect_fcu_not_syncing_or_accepted(
300    response: &ForkchoiceUpdated,
301    context: &str,
302) -> Result<()> {
303    match &response.payload_status.status {
304        PayloadStatusEnum::Valid => {
305            debug!("{}: FCU status is VALID as expected (not SYNCING or ACCEPTED).", context);
306            Ok(())
307        }
308        PayloadStatusEnum::Invalid { validation_error } => {
309            debug!(
310                "{}: FCU status is INVALID as expected (not SYNCING or ACCEPTED): {:?}",
311                context, validation_error
312            );
313            Ok(())
314        }
315        syncing_or_accepted_status @ (PayloadStatusEnum::Syncing | PayloadStatusEnum::Accepted) => {
316            Err(eyre::eyre!(
317                "{}: Expected FCU status not SYNCING or ACCEPTED (i.e., VALID or INVALID), but got {:?}",
318                context,
319                syncing_or_accepted_status
320            ))
321        }
322    }
323}