Skip to main content

reth_e2e_test_utils/
engine.rs

1//! Version-agnostic access to the consensus engine of a test node.
2
3use alloy_primitives::Bytes;
4use alloy_rpc_types_engine::{ForkchoiceState, ForkchoiceUpdated, PayloadStatus};
5use reth_engine_primitives::{
6    BeaconForkChoiceUpdateError, BeaconOnNewPayloadError, ConsensusEngineHandle,
7};
8use reth_payload_primitives::{BuiltPayload, PayloadTypes};
9use reth_primitives_traits::{NodePrimitives, SealedBlock};
10
11/// Sends `newPayload` and `forkchoiceUpdated` messages to the consensus engine of a test node like
12/// a consensus client, without picking an Engine API version for the active fork.
13///
14/// The messages go to the engine in-process and skip the Engine API RPC layer. That layer decodes
15/// the JSON or SSZ request, rejects a method version that does not match the fork of the payload
16/// or payload attributes timestamp, checks that the fields of that fork are present, and maps the
17/// errors of the engine to RPC error codes. None of this happens here, so tests of that behaviour
18/// must call the versioned RPC methods, e.g. with [`EngineApiClient`] on the client of
19/// [`NodeTestContext::auth_server_handle`].
20///
21/// [`EngineApiClient`]: reth_rpc_api::EngineApiClient
22/// [`NodeTestContext::auth_server_handle`]: crate::node::NodeTestContext::auth_server_handle
23#[derive(Debug, Clone)]
24pub struct EngineTestContext<T: PayloadTypes> {
25    handle: ConsensusEngineHandle<T>,
26}
27
28impl<T: PayloadTypes> EngineTestContext<T> {
29    /// Creates a new engine context that sends its messages to the given engine handle.
30    pub const fn new(handle: ConsensusEngineHandle<T>) -> Self {
31        Self { handle }
32    }
33
34    /// Sends a payload to the engine like `engine_newPayload` and returns the payload status.
35    ///
36    /// The engine inserts the block without making it canonical, see [`Self::forkchoice_updated`].
37    /// An invalid block is reported as an `INVALID` status, not as an error. A built payload of the
38    /// node converts into the payload type of the engine.
39    ///
40    /// Returns an error if the engine can not process the payload at all, e.g. because its
41    /// parameters are malformed, which the Engine API returns as an invalid params error.
42    pub async fn new_payload(
43        &self,
44        payload: impl Into<T::ExecutionData>,
45    ) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
46        self.handle.new_payload(payload.into()).await
47    }
48
49    /// Sends the payload of a sealed block to the engine, see [`Self::new_payload`].
50    ///
51    /// `block_access_list` is the encoded block access list of the block, which payloads carry
52    /// from Amsterdam on.
53    pub async fn new_payload_from_block(
54        &self,
55        block: SealedBlock<
56            <<T::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
57        >,
58        block_access_list: Option<Bytes>,
59    ) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
60        self.new_payload(T::block_to_payload(block, block_access_list)).await
61    }
62
63    /// Sends a forkchoice update without payload attributes to the engine like
64    /// `engine_forkchoiceUpdated` and returns the response.
65    ///
66    /// The payload status of the response is the outcome for the head block: `VALID` once it is
67    /// canonical, `SYNCING` if the engine does not know it or is backfilling, and `INVALID` if it
68    /// is or descends from an invalid block.
69    ///
70    /// Returns an error if the engine rejects the forkchoice state, e.g.
71    /// [`ForkchoiceUpdateError::InvalidState`] for a zero head hash, or a safe or finalized block
72    /// that is unknown or not on the chain of the head. The Engine API returns that error as code
73    /// -38002.
74    ///
75    /// [`ForkchoiceUpdateError::InvalidState`]: alloy_rpc_types_engine::ForkchoiceUpdateError::InvalidState
76    pub async fn forkchoice_updated(
77        &self,
78        state: ForkchoiceState,
79    ) -> Result<ForkchoiceUpdated, BeaconForkChoiceUpdateError> {
80        self.handle.fork_choice_updated(state, None).await
81    }
82
83    /// Sends a forkchoice update with payload attributes to the engine like
84    /// `engine_forkchoiceUpdated`, which starts a payload job on top of the head, and returns the
85    /// response with the id of the job.
86    ///
87    /// The forkchoice state is handled like in [`Self::forkchoice_updated`]. The response has no
88    /// payload id if the engine starts no payload job, e.g. because it is syncing.
89    ///
90    /// Returns [`ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes`] if the attributes are
91    /// invalid for the head, e.g. because their timestamp is not greater than the timestamp of the
92    /// head. The engine still applies the forkchoice state in that case. The Engine API returns
93    /// that error as code -38003.
94    ///
95    /// [`ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes`]: alloy_rpc_types_engine::ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes
96    pub async fn forkchoice_updated_with_attributes(
97        &self,
98        state: ForkchoiceState,
99        attributes: impl Into<T::PayloadAttributes>,
100    ) -> Result<ForkchoiceUpdated, BeaconForkChoiceUpdateError> {
101        self.handle.fork_choice_updated(state, Some(attributes.into())).await
102    }
103}