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 `engine_*` RPC methods, e.g. with the client of
19/// [`NodeTestContext::auth_server_handle`].
20///
21/// [`NodeTestContext::auth_server_handle`]: crate::node::NodeTestContext::auth_server_handle
22#[derive(Debug, Clone)]
23pub struct EngineTestContext<T: PayloadTypes> {
24    handle: ConsensusEngineHandle<T>,
25}
26
27impl<T: PayloadTypes> EngineTestContext<T> {
28    /// Creates a new engine context that sends its messages to the given engine handle.
29    pub const fn new(handle: ConsensusEngineHandle<T>) -> Self {
30        Self { handle }
31    }
32
33    /// Sends a payload to the engine like `engine_newPayload` and returns the payload status.
34    ///
35    /// The engine inserts the block without making it canonical, see [`Self::forkchoice_updated`].
36    /// An invalid block is reported as an `INVALID` status, not as an error. A built payload of the
37    /// node converts into the payload type of the engine.
38    ///
39    /// Returns an error if the engine can not process the payload at all, e.g. because its
40    /// parameters are malformed, which the Engine API returns as an invalid params error.
41    pub async fn new_payload(
42        &self,
43        payload: impl Into<T::ExecutionData>,
44    ) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
45        self.handle.new_payload(payload.into()).await
46    }
47
48    /// Sends the payload of a sealed block to the engine, see [`Self::new_payload`].
49    ///
50    /// `block_access_list` is the encoded block access list of the block, which payloads carry
51    /// from Amsterdam on.
52    pub async fn new_payload_from_block(
53        &self,
54        block: SealedBlock<
55            <<T::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
56        >,
57        block_access_list: Option<Bytes>,
58    ) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
59        self.new_payload(T::block_to_payload(block, block_access_list)).await
60    }
61
62    /// Sends a forkchoice update without payload attributes to the engine like
63    /// `engine_forkchoiceUpdated` and returns the response.
64    ///
65    /// The payload status of the response is the outcome for the head block: `VALID` once it is
66    /// canonical, `SYNCING` if the engine does not know it or is backfilling, and `INVALID` if it
67    /// is or descends from an invalid block.
68    ///
69    /// Returns an error if the engine rejects the forkchoice state, e.g.
70    /// [`ForkchoiceUpdateError::InvalidState`] for a zero head hash, or a safe or finalized block
71    /// that is unknown or not on the chain of the head. The Engine API returns that error as code
72    /// -38002.
73    ///
74    /// [`ForkchoiceUpdateError::InvalidState`]: alloy_rpc_types_engine::ForkchoiceUpdateError::InvalidState
75    pub async fn forkchoice_updated(
76        &self,
77        state: ForkchoiceState,
78    ) -> Result<ForkchoiceUpdated, BeaconForkChoiceUpdateError> {
79        self.handle.fork_choice_updated(state, None).await
80    }
81
82    /// Sends a forkchoice update with payload attributes to the engine like
83    /// `engine_forkchoiceUpdated`, which starts a payload job on top of the head, and returns the
84    /// response with the id of the job.
85    ///
86    /// The forkchoice state is handled like in [`Self::forkchoice_updated`]. The response has no
87    /// payload id if the engine starts no payload job, e.g. because it is syncing.
88    ///
89    /// Returns [`ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes`] if the attributes are
90    /// invalid for the head, e.g. because their timestamp is not greater than the timestamp of the
91    /// head. The engine still applies the forkchoice state in that case. The Engine API returns
92    /// that error as code -38003.
93    ///
94    /// [`ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes`]: alloy_rpc_types_engine::ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes
95    pub async fn forkchoice_updated_with_attributes(
96        &self,
97        state: ForkchoiceState,
98        attributes: impl Into<T::PayloadAttributes>,
99    ) -> Result<ForkchoiceUpdated, BeaconForkChoiceUpdateError> {
100        self.handle.fork_choice_updated(state, Some(attributes.into())).await
101    }
102}