reth_optimism_rpc/
engine.rs

1//! Implements the Optimism engine API RPC methods.
2
3use alloy_eips::eip7685::Requests;
4use alloy_primitives::{BlockHash, B256, B64, U64};
5use alloy_rpc_types_engine::{
6    ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV3,
7    ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus,
8};
9use derive_more::Constructor;
10use jsonrpsee::proc_macros::rpc;
11use jsonrpsee_core::{server::RpcModule, RpcResult};
12use op_alloy_rpc_types_engine::{
13    OpExecutionData, OpExecutionPayloadV4, ProtocolVersion, ProtocolVersionFormatV0,
14    SuperchainSignal,
15};
16use reth_chainspec::EthereumHardforks;
17use reth_node_api::{EngineTypes, EngineValidator};
18use reth_rpc_api::IntoEngineApiRpcModule;
19use reth_rpc_engine_api::EngineApi;
20use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory};
21use reth_transaction_pool::TransactionPool;
22use tracing::{info, trace};
23
24/// The list of all supported Engine capabilities available over the engine endpoint.
25///
26/// Spec: <https://specs.optimism.io/protocol/exec-engine.html>
27pub const OP_ENGINE_CAPABILITIES: &[&str] = &[
28    "engine_forkchoiceUpdatedV1",
29    "engine_forkchoiceUpdatedV2",
30    "engine_forkchoiceUpdatedV3",
31    "engine_getClientVersionV1",
32    "engine_getPayloadV2",
33    "engine_getPayloadV3",
34    "engine_getPayloadV4",
35    "engine_newPayloadV2",
36    "engine_newPayloadV3",
37    "engine_newPayloadV4",
38    "engine_getPayloadBodiesByHashV1",
39    "engine_getPayloadBodiesByRangeV1",
40    "engine_signalSuperchainV1",
41];
42
43/// OP Stack protocol version
44/// See also: <https://github.com/ethereum-optimism/op-geth/blob/c3a989eb882d150a936df27bcfa791838b474d55/params/superchain.go#L13-L13>
45pub const OP_STACK_SUPPORT: ProtocolVersion = ProtocolVersion::V0(ProtocolVersionFormatV0 {
46    build: B64::ZERO,
47    major: 9,
48    minor: 0,
49    patch: 0,
50    pre_release: 0,
51});
52
53/// Extension trait that gives access to Optimism engine API RPC methods.
54///
55/// Note:
56/// > The provider should use a JWT authentication layer.
57///
58/// This follows the Optimism specs that can be found at:
59/// <https://specs.optimism.io/protocol/exec-engine.html#engine-api>
60#[cfg_attr(not(feature = "client"), rpc(server, namespace = "engine"), server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned))]
61#[cfg_attr(feature = "client", rpc(server, client, namespace = "engine", client_bounds(Engine::PayloadAttributes: jsonrpsee::core::Serialize + Clone), server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned)))]
62pub trait OpEngineApi<Engine: EngineTypes> {
63    /// Sends the given payload to the execution layer client, as specified for the Shanghai fork.
64    ///
65    /// See also <https://github.com/ethereum/execution-apis/blob/584905270d8ad665718058060267061ecfd79ca5/src/engine/shanghai.md#engine_newpayloadv2>
66    ///
67    /// No modifications needed for OP compatibility.
68    #[method(name = "newPayloadV2")]
69    async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult<PayloadStatus>;
70
71    /// Sends the given payload to the execution layer client, as specified for the Cancun fork.
72    ///
73    /// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_newpayloadv3>
74    ///
75    /// OP modifications:
76    /// - expected versioned hashes MUST be an empty array: therefore the `versioned_hashes`
77    ///   parameter is removed.
78    /// - parent beacon block root MUST be the parent beacon block root from the L1 origin block of
79    ///   the L2 block.
80    /// - blob versioned hashes MUST be empty list.
81    #[method(name = "newPayloadV3")]
82    async fn new_payload_v3(
83        &self,
84        payload: ExecutionPayloadV3,
85        versioned_hashes: Vec<B256>,
86        parent_beacon_block_root: B256,
87    ) -> RpcResult<PayloadStatus>;
88
89    /// Sends the given payload to the execution layer client, as specified for the Prague fork.
90    ///
91    /// See also <https://github.com/ethereum/execution-apis/blob/03911ffc053b8b806123f1fc237184b0092a485a/src/engine/prague.md#engine_newpayloadv4>
92    ///
93    /// - blob versioned hashes MUST be empty list.
94    /// - execution layer requests MUST be empty list.
95    #[method(name = "newPayloadV4")]
96    async fn new_payload_v4(
97        &self,
98        payload: OpExecutionPayloadV4,
99        versioned_hashes: Vec<B256>,
100        parent_beacon_block_root: B256,
101        execution_requests: Requests,
102    ) -> RpcResult<PayloadStatus>;
103
104    /// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_forkchoiceupdatedv1>
105    ///
106    /// This exists because it is used by op-node: <https://github.com/ethereum-optimism/optimism/blob/0bc5fe8d16155dc68bcdf1fa5733abc58689a618/op-node/rollup/types.go#L615-L617>
107    ///
108    /// Caution: This should not accept the `withdrawals` field in the payload attributes.
109    #[method(name = "forkchoiceUpdatedV1")]
110    async fn fork_choice_updated_v1(
111        &self,
112        fork_choice_state: ForkchoiceState,
113        payload_attributes: Option<Engine::PayloadAttributes>,
114    ) -> RpcResult<ForkchoiceUpdated>;
115
116    /// Updates the execution layer client with the given fork choice, as specified for the Shanghai
117    /// fork.
118    ///
119    /// Caution: This should not accept the `parentBeaconBlockRoot` field in the payload attributes.
120    ///
121    /// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#engine_forkchoiceupdatedv2>
122    ///
123    /// OP modifications:
124    /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in <https://specs.optimism.io/protocol/exec-engine.html#extended-payloadattributesv2>
125    #[method(name = "forkchoiceUpdatedV2")]
126    async fn fork_choice_updated_v2(
127        &self,
128        fork_choice_state: ForkchoiceState,
129        payload_attributes: Option<Engine::PayloadAttributes>,
130    ) -> RpcResult<ForkchoiceUpdated>;
131
132    /// Updates the execution layer client with the given fork choice, as specified for the Cancun
133    /// fork.
134    ///
135    /// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_forkchoiceupdatedv3>
136    ///
137    /// OP modifications:
138    /// - Must be called with an Ecotone payload
139    /// - Attributes must contain the parent beacon block root field
140    /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in <https://specs.optimism.io/protocol/exec-engine.html#extended-payloadattributesv2>
141    #[method(name = "forkchoiceUpdatedV3")]
142    async fn fork_choice_updated_v3(
143        &self,
144        fork_choice_state: ForkchoiceState,
145        payload_attributes: Option<Engine::PayloadAttributes>,
146    ) -> RpcResult<ForkchoiceUpdated>;
147
148    /// Retrieves an execution payload from a previously started build process, as specified for the
149    /// Shanghai fork.
150    ///
151    /// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#engine_getpayloadv2>
152    ///
153    /// Note:
154    /// > Provider software MAY stop the corresponding build process after serving this call.
155    ///
156    /// No modifications needed for OP compatibility.
157    #[method(name = "getPayloadV2")]
158    async fn get_payload_v2(
159        &self,
160        payload_id: PayloadId,
161    ) -> RpcResult<Engine::ExecutionPayloadEnvelopeV2>;
162
163    /// Retrieves an execution payload from a previously started build process, as specified for the
164    /// Cancun fork.
165    ///
166    /// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_getpayloadv3>
167    ///
168    /// Note:
169    /// > Provider software MAY stop the corresponding build process after serving this call.
170    ///
171    /// OP modifications:
172    /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV3`].
173    #[method(name = "getPayloadV3")]
174    async fn get_payload_v3(
175        &self,
176        payload_id: PayloadId,
177    ) -> RpcResult<Engine::ExecutionPayloadEnvelopeV3>;
178
179    /// Returns the most recent version of the payload that is available in the corresponding
180    /// payload build process at the time of receiving this call.
181    ///
182    /// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#engine_getpayloadv4>
183    ///
184    /// Note:
185    /// > Provider software MAY stop the corresponding build process after serving this call.
186    ///
187    /// OP modifications:
188    /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV4`].
189    #[method(name = "getPayloadV4")]
190    async fn get_payload_v4(
191        &self,
192        payload_id: PayloadId,
193    ) -> RpcResult<Engine::ExecutionPayloadEnvelopeV4>;
194
195    /// Returns the execution payload bodies by the given hash.
196    ///
197    /// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#engine_getpayloadbodiesbyhashv1>
198    #[method(name = "getPayloadBodiesByHashV1")]
199    async fn get_payload_bodies_by_hash_v1(
200        &self,
201        block_hashes: Vec<BlockHash>,
202    ) -> RpcResult<ExecutionPayloadBodiesV1>;
203
204    /// Returns the execution payload bodies by the range starting at `start`, containing `count`
205    /// blocks.
206    ///
207    /// WARNING: This method is associated with the BeaconBlocksByRange message in the consensus
208    /// layer p2p specification, meaning the input should be treated as untrusted or potentially
209    /// adversarial.
210    ///
211    /// Implementers should take care when acting on the input to this method, specifically
212    /// ensuring that the range is limited properly, and that the range boundaries are computed
213    /// correctly and without panics.
214    ///
215    /// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#engine_getpayloadbodiesbyrangev1>
216    #[method(name = "getPayloadBodiesByRangeV1")]
217    async fn get_payload_bodies_by_range_v1(
218        &self,
219        start: U64,
220        count: U64,
221    ) -> RpcResult<ExecutionPayloadBodiesV1>;
222
223    /// Signals superchain information to the Engine.
224    /// Returns the latest supported OP-Stack protocol version of the execution engine.
225    /// See also <https://specs.optimism.io/protocol/exec-engine.html#engine_signalsuperchainv1>
226    #[method(name = "engine_signalSuperchainV1")]
227    async fn signal_superchain_v1(&self, _signal: SuperchainSignal) -> RpcResult<ProtocolVersion>;
228
229    /// Returns the execution client version information.
230    ///
231    /// Note:
232    /// > The `client_version` parameter identifies the consensus client.
233    ///
234    /// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/identification.md#engine_getclientversionv1>
235    #[method(name = "getClientVersionV1")]
236    async fn get_client_version_v1(
237        &self,
238        client_version: ClientVersionV1,
239    ) -> RpcResult<Vec<ClientVersionV1>>;
240
241    /// Returns the list of Engine API methods supported by the execution layer client software.
242    ///
243    /// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/common.md#capabilities>
244    #[method(name = "exchangeCapabilities")]
245    async fn exchange_capabilities(&self, capabilities: Vec<String>) -> RpcResult<Vec<String>>;
246}
247
248/// The Engine API implementation that grants the Consensus layer access to data and
249/// functions in the Execution layer that are crucial for the consensus process.
250#[derive(Debug, Constructor)]
251pub struct OpEngineApi<Provider, EngineT: EngineTypes, Pool, Validator, ChainSpec> {
252    inner: EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>,
253}
254
255#[async_trait::async_trait]
256impl<Provider, EngineT, Pool, Validator, ChainSpec> OpEngineApiServer<EngineT>
257    for OpEngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
258where
259    Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static,
260    EngineT: EngineTypes<ExecutionData = OpExecutionData>,
261    Pool: TransactionPool + 'static,
262    Validator: EngineValidator<EngineT>,
263    ChainSpec: EthereumHardforks + Send + Sync + 'static,
264{
265    async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult<PayloadStatus> {
266        trace!(target: "rpc::engine", "Serving engine_newPayloadV2");
267        let payload = OpExecutionData::v2(payload);
268        Ok(self.inner.new_payload_v2_metered(payload).await?)
269    }
270
271    async fn new_payload_v3(
272        &self,
273        payload: ExecutionPayloadV3,
274        versioned_hashes: Vec<B256>,
275        parent_beacon_block_root: B256,
276    ) -> RpcResult<PayloadStatus> {
277        trace!(target: "rpc::engine", "Serving engine_newPayloadV3");
278        let payload = OpExecutionData::v3(payload, versioned_hashes, parent_beacon_block_root);
279
280        Ok(self.inner.new_payload_v3_metered(payload).await?)
281    }
282
283    async fn new_payload_v4(
284        &self,
285        payload: OpExecutionPayloadV4,
286        versioned_hashes: Vec<B256>,
287        parent_beacon_block_root: B256,
288        execution_requests: Requests,
289    ) -> RpcResult<PayloadStatus> {
290        trace!(target: "rpc::engine", "Serving engine_newPayloadV4");
291        let payload = OpExecutionData::v4(
292            payload,
293            versioned_hashes,
294            parent_beacon_block_root,
295            execution_requests,
296        );
297
298        Ok(self.inner.new_payload_v4_metered(payload).await?)
299    }
300
301    async fn fork_choice_updated_v1(
302        &self,
303        fork_choice_state: ForkchoiceState,
304        payload_attributes: Option<EngineT::PayloadAttributes>,
305    ) -> RpcResult<ForkchoiceUpdated> {
306        Ok(self.inner.fork_choice_updated_v1_metered(fork_choice_state, payload_attributes).await?)
307    }
308
309    async fn fork_choice_updated_v2(
310        &self,
311        fork_choice_state: ForkchoiceState,
312        payload_attributes: Option<EngineT::PayloadAttributes>,
313    ) -> RpcResult<ForkchoiceUpdated> {
314        trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV2");
315        Ok(self.inner.fork_choice_updated_v2_metered(fork_choice_state, payload_attributes).await?)
316    }
317
318    async fn fork_choice_updated_v3(
319        &self,
320        fork_choice_state: ForkchoiceState,
321        payload_attributes: Option<EngineT::PayloadAttributes>,
322    ) -> RpcResult<ForkchoiceUpdated> {
323        trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV3");
324        Ok(self.inner.fork_choice_updated_v3_metered(fork_choice_state, payload_attributes).await?)
325    }
326
327    async fn get_payload_v2(
328        &self,
329        payload_id: PayloadId,
330    ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV2> {
331        trace!(target: "rpc::engine", "Serving engine_getPayloadV2");
332        Ok(self.inner.get_payload_v2_metered(payload_id).await?)
333    }
334
335    async fn get_payload_v3(
336        &self,
337        payload_id: PayloadId,
338    ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV3> {
339        trace!(target: "rpc::engine", "Serving engine_getPayloadV3");
340        Ok(self.inner.get_payload_v3_metered(payload_id).await?)
341    }
342
343    async fn get_payload_v4(
344        &self,
345        payload_id: PayloadId,
346    ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV4> {
347        trace!(target: "rpc::engine", "Serving engine_getPayloadV4");
348        Ok(self.inner.get_payload_v4_metered(payload_id).await?)
349    }
350
351    async fn get_payload_bodies_by_hash_v1(
352        &self,
353        block_hashes: Vec<BlockHash>,
354    ) -> RpcResult<ExecutionPayloadBodiesV1> {
355        trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV1");
356        Ok(self.inner.get_payload_bodies_by_hash_v1_metered(block_hashes).await?)
357    }
358
359    async fn get_payload_bodies_by_range_v1(
360        &self,
361        start: U64,
362        count: U64,
363    ) -> RpcResult<ExecutionPayloadBodiesV1> {
364        trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV1");
365        Ok(self.inner.get_payload_bodies_by_range_v1_metered(start.to(), count.to()).await?)
366    }
367
368    async fn signal_superchain_v1(&self, signal: SuperchainSignal) -> RpcResult<ProtocolVersion> {
369        trace!(target: "rpc::engine", "Serving signal_superchain_v1");
370        info!(
371            target: "rpc::engine",
372            "Received superchain version signal local={:?} required={:?} recommended={:?}",
373            OP_STACK_SUPPORT,
374            signal.required,
375            signal.recommended
376        );
377        Ok(OP_STACK_SUPPORT)
378    }
379
380    async fn get_client_version_v1(
381        &self,
382        client: ClientVersionV1,
383    ) -> RpcResult<Vec<ClientVersionV1>> {
384        trace!(target: "rpc::engine", "Serving engine_getClientVersionV1");
385        Ok(self.inner.get_client_version_v1(client)?)
386    }
387
388    async fn exchange_capabilities(&self, _capabilities: Vec<String>) -> RpcResult<Vec<String>> {
389        Ok(self.inner.capabilities().list())
390    }
391}
392
393impl<Provider, EngineT, Pool, Validator, ChainSpec> IntoEngineApiRpcModule
394    for OpEngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
395where
396    EngineT: EngineTypes,
397    Self: OpEngineApiServer<EngineT>,
398{
399    fn into_rpc_module(self) -> RpcModule<()> {
400        self.into_rpc().remove_context()
401    }
402}