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}