feat(isthmus): Integrate OpExecutionPayloadValidator (#14207)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: caglarkaya <caglaryucekaya@gmail.com>
This commit is contained in:
Emilia Hane
2025-02-28 12:21:31 +01:00
committed by GitHub
parent 6abe4407fb
commit ffabf7ebf7
16 changed files with 137 additions and 96 deletions

View File

@@ -61,6 +61,7 @@ alloy-consensus.workspace = true
alloy-primitives.workspace = true
alloy-rpc-types = { workspace = true, features = ["engine"] }
alloy-eips = { workspace = true, features = ["kzg"] }
alloy-rpc-types-engine.workspace = true
## async
futures.workspace = true

View File

@@ -8,7 +8,8 @@ use std::{
use crate::{BeaconConsensusEngineEvent, BeaconConsensusEngineHandle, EthApiBuilderCtx};
use alloy_primitives::map::HashSet;
use alloy_rpc_types::engine::{ClientVersionV1, ExecutionData};
use alloy_rpc_types::engine::ClientVersionV1;
use alloy_rpc_types_engine::ExecutionData;
use futures::TryFutureExt;
use reth_chainspec::EthereumHardforks;
use reth_node_api::{

View File

@@ -35,6 +35,8 @@ reth-rpc-eth-api.workspace = true
reth-rpc-eth-types.workspace = true
reth-tasks = { workspace = true, optional = true }
reth-trie-common.workspace = true
reth-node-core.workspace = true
reth-rpc-engine-api.workspace = true
# op-reth
reth-optimism-payload-builder.workspace = true

View File

@@ -1,10 +1,9 @@
use alloy_consensus::BlockHeader;
use alloy_primitives::B256;
use alloy_rpc_types_engine::{
ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV2, ExecutionPayloadV1,
};
use alloy_rpc_types_engine::{ExecutionPayloadEnvelopeV2, ExecutionPayloadV1};
use op_alloy_rpc_types_engine::{
OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpPayloadAttributes,
OpExecutionData, OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4,
OpPayloadAttributes,
};
use reth_chainspec::ChainSpec;
use reth_consensus::ConsensusError;
@@ -20,9 +19,10 @@ use reth_node_api::{
use reth_optimism_chainspec::OpChainSpec;
use reth_optimism_consensus::isthmus;
use reth_optimism_forks::{OpHardfork, OpHardforks};
use reth_optimism_payload_builder::{OpBuiltPayload, OpPayloadBuilderAttributes};
use reth_optimism_payload_builder::{
OpBuiltPayload, OpExecutionPayloadValidator, OpPayloadBuilderAttributes,
};
use reth_optimism_primitives::{OpBlock, OpPrimitives, ADDRESS_L2_TO_L1_MESSAGE_PASSER};
use reth_payload_validator::ExecutionPayloadValidator;
use reth_primitives::{RecoveredBlock, SealedBlock};
use reth_provider::StateProviderFactory;
use reth_trie_common::{HashedPostState, KeyHasher};
@@ -53,16 +53,14 @@ where
type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2;
type ExecutionPayloadEnvelopeV3 = OpExecutionPayloadEnvelopeV3;
type ExecutionPayloadEnvelopeV4 = OpExecutionPayloadEnvelopeV4;
type ExecutionData = ExecutionData;
type ExecutionData = OpExecutionData;
fn block_to_payload(
block: SealedBlock<
<<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
>,
) -> ExecutionData {
let (payload, sidecar) =
ExecutionPayload::from_block_unchecked(block.hash(), &block.into_block());
ExecutionData { payload, sidecar }
) -> OpExecutionData {
OpExecutionData::from_block_unchecked(block.hash(), &block.into_block())
}
}
@@ -80,7 +78,7 @@ impl<N: NodePrimitives> PayloadTypes for OpPayloadTypes<N> {
/// Validator for Optimism engine API.
#[derive(Debug, Clone)]
pub struct OpEngineValidator<P> {
inner: ExecutionPayloadValidator<OpChainSpec>,
inner: OpExecutionPayloadValidator<OpChainSpec>,
provider: P,
hashed_addr_l2tol1_msg_passer: B256,
}
@@ -90,7 +88,7 @@ impl<P> OpEngineValidator<P> {
pub fn new<KH: KeyHasher>(chain_spec: Arc<OpChainSpec>, provider: P) -> Self {
let hashed_addr_l2tol1_msg_passer = KH::hash_key(ADDRESS_L2_TO_L1_MESSAGE_PASSER);
Self {
inner: ExecutionPayloadValidator::new(chain_spec),
inner: OpExecutionPayloadValidator::new(chain_spec),
provider,
hashed_addr_l2tol1_msg_passer,
}
@@ -108,13 +106,14 @@ where
P: StateProviderFactory + Unpin + 'static,
{
type Block = OpBlock;
type ExecutionData = ExecutionData;
type ExecutionData = OpExecutionData;
fn ensure_well_formed_payload(
&self,
payload: ExecutionData,
payload: Self::ExecutionData,
) -> Result<RecoveredBlock<Self::Block>, NewPayloadError> {
let sealed_block = self.inner.ensure_well_formed_payload(payload)?;
let sealed_block =
self.inner.ensure_well_formed_payload(payload).map_err(NewPayloadError::other)?;
sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into()))
}
@@ -148,7 +147,7 @@ where
impl<Types, P> EngineValidator<Types> for OpEngineValidator<P>
where
Types: EngineTypes<PayloadAttributes = OpPayloadAttributes, ExecutionData = ExecutionData>,
Types: EngineTypes<PayloadAttributes = OpPayloadAttributes, ExecutionData = OpExecutionData>,
P: StateProviderFactory + Unpin + 'static,
{
fn validate_execution_requests(

View File

@@ -24,6 +24,9 @@ pub use node::{OpNetworkPrimitives, OpNode};
pub mod rpc;
pub use rpc::OpEngineApiBuilder;
pub mod version;
pub use version::OP_NAME_CLIENT;
pub use reth_optimism_txpool as txpool;
/// Helpers for running test node instances.

View File

@@ -1,19 +1,25 @@
//! RPC component builder
use reth_optimism_rpc::engine::OP_CAPABILITIES;
pub use reth_optimism_rpc::OpEngineApi;
use alloy_rpc_types_engine::ExecutionData;
use crate::OP_NAME_CLIENT;
use alloy_rpc_types_engine::ClientVersionV1;
use op_alloy_rpc_types_engine::OpExecutionData;
use reth_chainspec::EthereumHardforks;
use reth_node_api::{
AddOnsContext, EngineTypes, FullNodeComponents, NodeTypes, NodeTypesWithEngine,
};
use reth_node_builder::rpc::{BasicEngineApiBuilder, EngineApiBuilder, EngineValidatorBuilder};
use reth_node_builder::rpc::{EngineApiBuilder, EngineValidatorBuilder};
use reth_node_core::version::{CARGO_PKG_VERSION, CLIENT_CODE, VERGEN_GIT_SHA};
use reth_optimism_rpc::engine::op_capabilities;
use reth_payload_builder::PayloadStore;
use reth_rpc_engine_api::{EngineApi, EngineCapabilities};
/// Builder for basic [`OpEngineApi`] implementation.
#[derive(Debug, Default)]
pub struct OpEngineApiBuilder<EV> {
inner: BasicEngineApiBuilder<EV>,
engine_validator_builder: EV,
capabilities: Option<EngineCapabilities>,
}
impl<N, EV> EngineApiBuilder<N> for OpEngineApiBuilder<EV>
@@ -21,7 +27,7 @@ where
N: FullNodeComponents<
Types: NodeTypesWithEngine<
ChainSpec: EthereumHardforks,
Engine: EngineTypes<ExecutionData = ExecutionData>,
Engine: EngineTypes<ExecutionData = OpExecutionData>,
>,
>,
EV: EngineValidatorBuilder<N>,
@@ -35,7 +41,26 @@ where
>;
async fn build_engine_api(self, ctx: &AddOnsContext<'_, N>) -> eyre::Result<Self::EngineApi> {
let inner = self.inner.capabilities(OP_CAPABILITIES).build_engine_api(ctx).await?;
let Self { engine_validator_builder, capabilities } = self;
let engine_validator = engine_validator_builder.build(ctx).await?;
let client = ClientVersionV1 {
code: CLIENT_CODE,
name: OP_NAME_CLIENT.to_string(),
version: CARGO_PKG_VERSION.to_string(),
commit: VERGEN_GIT_SHA.to_string(),
};
let inner = EngineApi::new(
ctx.node.provider().clone(),
ctx.config.chain.clone(),
ctx.beacon_engine_handle.clone(),
PayloadStore::new(ctx.node.payload_builder_handle().clone()),
ctx.node.pool().clone(),
Box::new(ctx.node.task_executor().clone()),
client,
capabilities.unwrap_or_else(op_capabilities),
engine_validator,
);
Ok(OpEngineApi::new(inner))
}

View File

@@ -0,0 +1,4 @@
//! Version information for op-reth.
/// The human readable name of the client
pub const OP_NAME_CLIENT: &str = "OP-Reth";

View File

@@ -3,19 +3,21 @@
use alloy_eips::eip7685::Requests;
use alloy_primitives::{BlockHash, B256, U64};
use alloy_rpc_types_engine::{
ClientVersionV1, ExecutionData, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2,
ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus,
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::OpExecutionPayloadV4;
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, EngineApiServer};
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.
///
@@ -35,6 +37,13 @@ pub const OP_CAPABILITIES: &[&str] = &[
"engine_getPayloadBodiesByRangeV1",
];
/// Returns [`EngineCapabilities`] supported by OP stack.
pub fn op_capabilities() -> EngineCapabilities {
EngineCapabilities::new(
OP_CAPABILITIES.iter().map(|cap| cap.to_string()).collect::<HashSet<_>>(),
)
}
/// Extension trait that gives access to Optimism engine API RPC methods.
///
/// Note:
@@ -224,13 +233,15 @@ impl<Provider, EngineT, Pool, Validator, ChainSpec> OpEngineApiServer<EngineT>
for OpEngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
where
Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static,
EngineT: EngineTypes<ExecutionData = ExecutionData>,
EngineT: EngineTypes<ExecutionData = OpExecutionData>,
Pool: TransactionPool + 'static,
Validator: EngineValidator<EngineT>,
ChainSpec: EthereumHardforks + Send + Sync + 'static,
{
async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult<PayloadStatus> {
EngineApiServer::new_payload_v2(&self.inner, payload).await
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(
@@ -239,13 +250,10 @@ where
versioned_hashes: Vec<B256>,
parent_beacon_block_root: B256,
) -> RpcResult<PayloadStatus> {
EngineApiServer::new_payload_v3(
&self.inner,
payload,
versioned_hashes,
parent_beacon_block_root,
)
.await
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(
@@ -255,16 +263,15 @@ where
parent_beacon_block_root: B256,
execution_requests: Requests,
) -> RpcResult<PayloadStatus> {
// todo: custom op engine validator <https://github.com/paradigmxyz/reth/pull/14207>
let payload = payload.payload_inner;
EngineApiServer::new_payload_v4(
&self.inner,
trace!(target: "rpc::engine", "Serving engine_newPayloadV4");
let payload = OpExecutionData::v4(
payload,
versioned_hashes,
parent_beacon_block_root,
execution_requests,
)
.await
);
Ok(self.inner.new_payload_v4_metered(payload).await?)
}
async fn fork_choice_updated_v2(
@@ -272,8 +279,7 @@ where
fork_choice_state: ForkchoiceState,
payload_attributes: Option<EngineT::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated> {
EngineApiServer::fork_choice_updated_v2(&self.inner, fork_choice_state, payload_attributes)
.await
Ok(self.inner.fork_choice_updated_v2_metered(fork_choice_state, payload_attributes).await?)
}
async fn fork_choice_updated_v3(
@@ -281,36 +287,35 @@ where
fork_choice_state: ForkchoiceState,
payload_attributes: Option<EngineT::PayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated> {
EngineApiServer::fork_choice_updated_v3(&self.inner, fork_choice_state, payload_attributes)
.await
Ok(self.inner.fork_choice_updated_v3_metered(fork_choice_state, payload_attributes).await?)
}
async fn get_payload_v2(
&self,
payload_id: PayloadId,
) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV2> {
EngineApiServer::get_payload_v2(&self.inner, payload_id).await
Ok(self.inner.get_payload_v2_metered(payload_id).await?)
}
async fn get_payload_v3(
&self,
payload_id: PayloadId,
) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV3> {
EngineApiServer::get_payload_v3(&self.inner, payload_id).await
Ok(self.inner.get_payload_v3_metered(payload_id).await?)
}
async fn get_payload_v4(
&self,
payload_id: PayloadId,
) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV4> {
EngineApiServer::get_payload_v4(&self.inner, payload_id).await
Ok(self.inner.get_payload_v4_metered(payload_id).await?)
}
async fn get_payload_bodies_by_hash_v1(
&self,
block_hashes: Vec<BlockHash>,
) -> RpcResult<ExecutionPayloadBodiesV1> {
EngineApiServer::get_payload_bodies_by_hash_v1(&self.inner, block_hashes).await
Ok(self.inner.get_payload_bodies_by_hash_v1_metered(block_hashes).await?)
}
async fn get_payload_bodies_by_range_v1(
@@ -318,14 +323,14 @@ where
start: U64,
count: U64,
) -> RpcResult<ExecutionPayloadBodiesV1> {
EngineApiServer::get_payload_bodies_by_range_v1(&self.inner, start, count).await
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<Vec<ClientVersionV1>> {
EngineApiServer::get_client_version_v1(&self.inner, client).await
Ok(self.inner.get_client_version_v1(client)?)
}
async fn exchange_capabilities(&self, _capabilities: Vec<String>) -> RpcResult<Vec<String>> {

View File

@@ -17,7 +17,7 @@ pub mod witness;
#[cfg(feature = "client")]
pub use engine::OpEngineApiClient;
pub use engine::{OpEngineApi, OpEngineApiServer, OP_CAPABILITIES};
pub use engine::{op_capabilities, OpEngineApi, OpEngineApiServer, OP_CAPABILITIES};
pub use error::{OpEthApiError, OpInvalidTransactionError, SequencerClientError};
pub use eth::{OpEthApi, OpReceiptBuilder};
pub use sequencer::SequencerClient;

View File

@@ -130,8 +130,8 @@ pub enum NewPayloadError {
impl NewPayloadError {
/// Creates instance of variant [`NewPayloadError::Other`].
#[inline]
pub const fn other(err: Box<dyn error::Error + Send + Sync>) -> Self {
Self::Other(err)
pub fn other(err: impl error::Error + Send + Sync + 'static) -> Self {
Self::Other(Box::new(err))
}
}

View File

@@ -149,7 +149,7 @@ impl ExecutionPayload for op_alloy_rpc_types_engine::OpExecutionData {
}
fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
Some(&self.payload.as_v2().withdrawals)
self.payload.as_v2().map(|p| &p.withdrawals)
}
fn parent_beacon_block_root(&self) -> Option<B256> {
@@ -157,10 +157,10 @@ impl ExecutionPayload for op_alloy_rpc_types_engine::OpExecutionData {
}
fn timestamp(&self) -> u64 {
self.payload.as_v2().timestamp()
self.payload.as_v1().timestamp
}
fn gas_used(&self) -> u64 {
self.payload.as_v2().payload_inner.gas_used
self.payload.as_v1().gas_used
}
}

View File

@@ -8,7 +8,7 @@ use reth_primitives_traits::{AlloyBlockHeader, Block, SealedBlock};
///
/// Checks that:
/// - Cancun fields are present if Cancun is active
/// - contains EIP-4844 transactions if Cancun is active and vv
/// - doesn't contain EIP-4844 transactions unless Cancun is active
/// - checks blob versioned hashes in block and sidecar match
#[inline]
pub fn ensure_well_formed_fields<T, B>(
@@ -69,7 +69,7 @@ pub fn ensure_well_formed_header_and_sidecar_fields<T: Block>(
/// Checks transactions field and sidecar w.r.t new Cancun fields and new transaction type EIP-4844.
///
/// Checks that:
/// - contains EIP-4844 transactions if Cancun is active
/// - doesn't contain EIP-4844 transactions unless Cancun is active
/// - checks blob versioned hashes in block and sidecar match
#[inline]
pub fn ensure_well_formed_transactions_field_with_sidecar<T: Transaction + Typed2718>(

View File

@@ -122,7 +122,7 @@ where
}
/// Fetches the client version.
fn get_client_version_v1(
pub fn get_client_version_v1(
&self,
_client: ClientVersionV1,
) -> EngineApiResult<Vec<ClientVersionV1>> {
@@ -239,8 +239,8 @@ where
.inspect(|_| self.inner.on_new_payload_response())?)
}
// Metrics version of `new_payload_v3`
async fn new_payload_v3_metered(
/// Metrics version of `new_payload_v3`
pub async fn new_payload_v3_metered(
&self,
payload: EngineT::ExecutionData,
) -> RpcResult<PayloadStatus> {
@@ -277,7 +277,7 @@ where
}
/// Metrics version of `new_payload_v4`
async fn new_payload_v4_metered(
pub async fn new_payload_v4_metered(
&self,
payload: EngineT::ExecutionData,
) -> RpcResult<PayloadStatus> {
@@ -334,7 +334,7 @@ where
}
/// Metrics version of `fork_choice_updated_v2`
async fn fork_choice_updated_v2_metered(
pub async fn fork_choice_updated_v2_metered(
&self,
state: ForkchoiceState,
payload_attrs: Option<EngineT::PayloadAttributes>,
@@ -360,7 +360,7 @@ where
}
/// Metrics version of `fork_choice_updated_v3`
async fn fork_choice_updated_v3_metered(
pub async fn fork_choice_updated_v3_metered(
&self,
state: ForkchoiceState,
payload_attrs: Option<EngineT::PayloadAttributes>,
@@ -399,7 +399,7 @@ where
}
/// Metrics version of `get_payload_v1`
async fn get_payload_v1_metered(
pub async fn get_payload_v1_metered(
&self,
payload_id: PayloadId,
) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV1> {
@@ -445,7 +445,7 @@ where
}
/// Metrics version of `get_payload_v2`
async fn get_payload_v2_metered(
pub async fn get_payload_v2_metered(
&self,
payload_id: PayloadId,
) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV2> {
@@ -491,7 +491,7 @@ where
}
/// Metrics version of `get_payload_v3`
async fn get_payload_v3_metered(
pub async fn get_payload_v3_metered(
&self,
payload_id: PayloadId,
) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV3> {
@@ -537,7 +537,7 @@ where
}
/// Metrics version of `get_payload_v4`
async fn get_payload_v4_metered(
pub async fn get_payload_v4_metered(
&self,
payload_id: PayloadId,
) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV4> {
@@ -549,7 +549,7 @@ where
/// Fetches all the blocks for the provided range starting at `start`, containing `count`
/// blocks and returns the mapped payload bodies.
async fn get_payload_bodies_by_range_with<F, R>(
pub async fn get_payload_bodies_by_range_with<F, R>(
&self,
start: BlockNumber,
count: u64,
@@ -627,7 +627,7 @@ where
}
/// Metrics version of `get_payload_bodies_by_range_v1`
async fn get_payload_bodies_by_range_v1_metered(
pub async fn get_payload_bodies_by_range_v1_metered(
&self,
start: BlockNumber,
count: u64,
@@ -639,7 +639,7 @@ where
}
/// Called to retrieve execution payload bodies by hashes.
async fn get_payload_bodies_by_hash_with<F, R>(
pub async fn get_payload_bodies_by_hash_with<F, R>(
&self,
hashes: Vec<BlockHash>,
f: F,
@@ -689,7 +689,7 @@ where
}
/// Metrics version of `get_payload_bodies_by_hash_v1`
async fn get_payload_bodies_by_hash_v1_metered(
pub async fn get_payload_bodies_by_hash_v1_metered(
&self,
hashes: Vec<BlockHash>,
) -> EngineApiResult<ExecutionPayloadBodiesV1> {
@@ -1114,9 +1114,7 @@ where
client: ClientVersionV1,
) -> RpcResult<Vec<ClientVersionV1>> {
trace!(target: "rpc::engine", "Serving engine_getClientVersionV1");
let res = Self::get_client_version_v1(self, client);
Ok(res?)
Ok(Self::get_client_version_v1(self, client)?)
}
/// Handler for `engine_exchangeCapabilitiesV1`

View File

@@ -14,6 +14,7 @@ mod engine_api;
/// Engine API capabilities.
pub mod capabilities;
pub use capabilities::EngineCapabilities;
/// Engine API error.
mod error;