//! Implements the Optimism engine API RPC methods. use alloy_eips::eip7685::Requests; use alloy_primitives::{BlockHash, B256, U64}; use alloy_rpc_types_engine::{ ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, }; use derive_more::Constructor; use jsonrpsee::proc_macros::rpc; use jsonrpsee_core::{server::RpcModule, RpcResult}; use op_alloy_rpc_types_engine::{OpExecutionData, OpExecutionPayloadV4}; use reth_chainspec::EthereumHardforks; use reth_node_api::{EngineTypes, EngineValidator}; use reth_provider::{BlockReader, HeaderProvider, StateProviderFactory}; use reth_rpc_api::IntoEngineApiRpcModule; use reth_rpc_engine_api::{EngineApi, EngineCapabilities}; use reth_transaction_pool::TransactionPool; use std::collections::HashSet; use tracing::trace; /// The list of all supported Engine capabilities available over the engine endpoint. /// /// Spec: pub const OP_CAPABILITIES: &[&str] = &[ "engine_forkchoiceUpdatedV2", "engine_forkchoiceUpdatedV3", "engine_exchangeTransitionConfigurationV1", "engine_getClientVersionV1", "engine_getPayloadV2", "engine_getPayloadV3", "engine_getPayloadV4", "engine_newPayloadV2", "engine_newPayloadV3", "engine_newPayloadV4", "engine_getPayloadBodiesByHashV1", "engine_getPayloadBodiesByRangeV1", ]; /// Returns [`EngineCapabilities`] supported by OP stack. pub fn op_capabilities() -> EngineCapabilities { EngineCapabilities::new( OP_CAPABILITIES.iter().map(|cap| cap.to_string()).collect::>(), ) } /// Extension trait that gives access to Optimism engine API RPC methods. /// /// Note: /// > The provider should use a JWT authentication layer. /// /// This follows the Optimism specs that can be found at: /// #[cfg_attr(not(feature = "client"), rpc(server, namespace = "engine"), server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "engine", client_bounds(Engine::PayloadAttributes: jsonrpsee::core::Serialize + Clone), server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned)))] pub trait OpEngineApi { /// Sends the given payload to the execution layer client, as specified for the Shanghai fork. /// /// See also /// /// No modifications needed for OP compatibility. #[method(name = "newPayloadV2")] async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult; /// Sends the given payload to the execution layer client, as specified for the Cancun fork. /// /// See also /// /// OP modifications: /// - expected versioned hashes MUST be an empty array: therefore the `versioned_hashes` /// parameter is removed. /// - parent beacon block root MUST be the parent beacon block root from the L1 origin block of /// the L2 block. /// - blob versioned hashes MUST be empty list. #[method(name = "newPayloadV3")] async fn new_payload_v3( &self, payload: ExecutionPayloadV3, versioned_hashes: Vec, parent_beacon_block_root: B256, ) -> RpcResult; /// Sends the given payload to the execution layer client, as specified for the Prague fork. /// /// See also /// /// - blob versioned hashes MUST be empty list. /// - execution layer requests MUST be empty list. #[method(name = "newPayloadV4")] async fn new_payload_v4( &self, payload: OpExecutionPayloadV4, versioned_hashes: Vec, parent_beacon_block_root: B256, execution_requests: Requests, ) -> RpcResult; /// Updates the execution layer client with the given fork choice, as specified for the Shanghai /// fork. /// /// Caution: This should not accept the `parentBeaconBlockRoot` field in the payload attributes. /// /// See also /// /// OP modifications: /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in #[method(name = "forkchoiceUpdatedV2")] async fn fork_choice_updated_v2( &self, fork_choice_state: ForkchoiceState, payload_attributes: Option, ) -> RpcResult; /// Updates the execution layer client with the given fork choice, as specified for the Cancun /// fork. /// /// See also /// /// OP modifications: /// - Must be called with an Ecotone payload /// - Attributes must contain the parent beacon block root field /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in #[method(name = "forkchoiceUpdatedV3")] async fn fork_choice_updated_v3( &self, fork_choice_state: ForkchoiceState, payload_attributes: Option, ) -> RpcResult; /// Retrieves an execution payload from a previously started build process, as specified for the /// Shanghai fork. /// /// See also /// /// Note: /// > Provider software MAY stop the corresponding build process after serving this call. /// /// No modifications needed for OP compatibility. #[method(name = "getPayloadV2")] async fn get_payload_v2( &self, payload_id: PayloadId, ) -> RpcResult; /// Retrieves an execution payload from a previously started build process, as specified for the /// Cancun fork. /// /// See also /// /// Note: /// > Provider software MAY stop the corresponding build process after serving this call. /// /// OP modifications: /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV3`]. #[method(name = "getPayloadV3")] async fn get_payload_v3( &self, payload_id: PayloadId, ) -> RpcResult; /// Returns the most recent version of the payload that is available in the corresponding /// payload build process at the time of receiving this call. /// /// See also /// /// Note: /// > Provider software MAY stop the corresponding build process after serving this call. /// /// OP modifications: /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV4`]. #[method(name = "getPayloadV4")] async fn get_payload_v4( &self, payload_id: PayloadId, ) -> RpcResult; /// Returns the execution payload bodies by the given hash. /// /// See also #[method(name = "getPayloadBodiesByHashV1")] async fn get_payload_bodies_by_hash_v1( &self, block_hashes: Vec, ) -> RpcResult; /// Returns the execution payload bodies by the range starting at `start`, containing `count` /// blocks. /// /// WARNING: This method is associated with the BeaconBlocksByRange message in the consensus /// layer p2p specification, meaning the input should be treated as untrusted or potentially /// adversarial. /// /// Implementers should take care when acting on the input to this method, specifically /// ensuring that the range is limited properly, and that the range boundaries are computed /// correctly and without panics. /// /// See also #[method(name = "getPayloadBodiesByRangeV1")] async fn get_payload_bodies_by_range_v1( &self, start: U64, count: U64, ) -> RpcResult; /// Returns the execution client version information. /// /// Note: /// > The `client_version` parameter identifies the consensus client. /// /// See also #[method(name = "getClientVersionV1")] async fn get_client_version_v1( &self, client_version: ClientVersionV1, ) -> RpcResult>; /// Returns the list of Engine API methods supported by the execution layer client software. /// /// See also #[method(name = "exchangeCapabilities")] async fn exchange_capabilities(&self, capabilities: Vec) -> RpcResult>; } /// The Engine API implementation that grants the Consensus layer access to data and /// functions in the Execution layer that are crucial for the consensus process. #[derive(Debug, Constructor)] pub struct OpEngineApi { inner: EngineApi, } #[async_trait::async_trait] impl OpEngineApiServer for OpEngineApi where Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, EngineT: EngineTypes, Pool: TransactionPool + 'static, Validator: EngineValidator, ChainSpec: EthereumHardforks + Send + Sync + 'static, { async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_newPayloadV2"); let payload = OpExecutionData::v2(payload); Ok(self.inner.new_payload_v2_metered(payload).await?) } async fn new_payload_v3( &self, payload: ExecutionPayloadV3, versioned_hashes: Vec, parent_beacon_block_root: B256, ) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_newPayloadV3"); let payload = OpExecutionData::v3(payload, versioned_hashes, parent_beacon_block_root); Ok(self.inner.new_payload_v3_metered(payload).await?) } async fn new_payload_v4( &self, payload: OpExecutionPayloadV4, versioned_hashes: Vec, parent_beacon_block_root: B256, execution_requests: Requests, ) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_newPayloadV4"); let payload = OpExecutionData::v4( payload, versioned_hashes, parent_beacon_block_root, execution_requests, ); Ok(self.inner.new_payload_v4_metered(payload).await?) } async fn fork_choice_updated_v2( &self, fork_choice_state: ForkchoiceState, payload_attributes: Option, ) -> RpcResult { Ok(self.inner.fork_choice_updated_v2_metered(fork_choice_state, payload_attributes).await?) } async fn fork_choice_updated_v3( &self, fork_choice_state: ForkchoiceState, payload_attributes: Option, ) -> RpcResult { Ok(self.inner.fork_choice_updated_v3_metered(fork_choice_state, payload_attributes).await?) } async fn get_payload_v2( &self, payload_id: PayloadId, ) -> RpcResult { Ok(self.inner.get_payload_v2_metered(payload_id).await?) } async fn get_payload_v3( &self, payload_id: PayloadId, ) -> RpcResult { Ok(self.inner.get_payload_v3_metered(payload_id).await?) } async fn get_payload_v4( &self, payload_id: PayloadId, ) -> RpcResult { Ok(self.inner.get_payload_v4_metered(payload_id).await?) } async fn get_payload_bodies_by_hash_v1( &self, block_hashes: Vec, ) -> RpcResult { Ok(self.inner.get_payload_bodies_by_hash_v1_metered(block_hashes).await?) } async fn get_payload_bodies_by_range_v1( &self, start: U64, count: U64, ) -> RpcResult { Ok(self.inner.get_payload_bodies_by_range_v1_metered(start.to(), count.to()).await?) } async fn get_client_version_v1( &self, client: ClientVersionV1, ) -> RpcResult> { Ok(self.inner.get_client_version_v1(client)?) } async fn exchange_capabilities(&self, _capabilities: Vec) -> RpcResult> { Ok(self.inner.capabilities().list()) } } impl IntoEngineApiRpcModule for OpEngineApi where EngineT: EngineTypes, Self: OpEngineApiServer, { fn into_rpc_module(self) -> RpcModule<()> { self.into_rpc().remove_context() } }