diff --git a/Cargo.lock b/Cargo.lock index d994a356f..b2354616a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8699,6 +8699,7 @@ dependencies = [ "reth-payload-builder-primitives", "reth-payload-primitives", "reth-payload-util", + "reth-payload-validator", "reth-primitives", "reth-primitives-traits", "reth-provider", diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index a6daa9769..8b95b5e79 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -27,6 +27,7 @@ reth-payload-util.workspace = true reth-payload-primitives = { workspace = true, features = ["op"] } reth-basic-payload-builder.workspace = true reth-chain-state.workspace = true +reth-payload-validator.workspace = true # op-reth reth-optimism-consensus.workspace = true diff --git a/crates/optimism/payload/src/lib.rs b/crates/optimism/payload/src/lib.rs index 659dc2350..61e7d10f5 100644 --- a/crates/optimism/payload/src/lib.rs +++ b/crates/optimism/payload/src/lib.rs @@ -9,6 +9,8 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![allow(clippy::useless_let_if_seq)] +extern crate alloc; + pub mod builder; pub use builder::OpPayloadBuilder; pub mod error; @@ -16,5 +18,7 @@ pub mod payload; pub use payload::{OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilderAttributes}; mod traits; pub use traits::*; +pub mod validator; +pub use validator::OpExecutionPayloadValidator; pub mod config; diff --git a/crates/optimism/payload/src/validator.rs b/crates/optimism/payload/src/validator.rs new file mode 100644 index 000000000..94928ce6f --- /dev/null +++ b/crates/optimism/payload/src/validator.rs @@ -0,0 +1,85 @@ +//! Validates execution payload wrt Optimism consensus rules + +use alloc::sync::Arc; +use alloy_rpc_types_engine::PayloadError; +use derive_more::{Constructor, Deref}; +use op_alloy_rpc_types_engine::{OpExecutionData, OpPayloadError}; +use reth_optimism_forks::OpHardforks; +use reth_payload_validator::{cancun, prague, shanghai}; +use reth_primitives::{Block, SealedBlock}; +use reth_primitives_traits::{Block as _, SignedTransaction}; + +/// Execution payload validator. +#[derive(Clone, Debug, Deref, Constructor)] +pub struct OpExecutionPayloadValidator { + /// Chain spec to validate against. + #[deref] + inner: Arc, +} + +impl OpExecutionPayloadValidator +where + ChainSpec: OpHardforks, +{ + /// Returns reference to chain spec. + pub fn chain_spec(&self) -> &ChainSpec { + &self.inner + } + + /// Ensures that the given payload does not violate any consensus rules that concern the block's + /// layout, like: + /// - missing or invalid base fee + /// - invalid extra data + /// - invalid transactions + /// - incorrect hash + /// - block contains blob transactions or blob versioned hashes + /// - block contains l1 withdrawals + /// + /// The checks are done in the order that conforms with the engine-API specification. + /// + /// This is intended to be invoked after receiving the payload from the CLI. + /// The additional fields, starting with [`MaybeCancunPayloadFields`](alloy_rpc_types_engine::MaybeCancunPayloadFields), are not part of the payload, but are additional fields starting in the `engine_newPayloadV3` RPC call, See also + /// + /// If the cancun fields are provided this also validates that the versioned hashes in the block + /// are empty as well as those passed in the sidecar. If the payload fields are not provided. + /// + /// Validation according to specs . + pub fn ensure_well_formed_payload( + &self, + payload: OpExecutionData, + ) -> Result>, OpPayloadError> { + let OpExecutionData { payload, sidecar } = payload; + + let expected_hash = payload.block_hash(); + + // First parse the block + let sealed_block = payload.try_into_block_with_sidecar(&sidecar)?.seal_slow(); + + // Ensure the hash included in the payload matches the block hash + if expected_hash != sealed_block.hash() { + return Err(PayloadError::BlockHash { + execution: sealed_block.hash(), + consensus: expected_hash, + })? + } + + shanghai::ensure_well_formed_fields( + sealed_block.body(), + self.is_shanghai_active_at_timestamp(sealed_block.timestamp), + )?; + + cancun::ensure_well_formed_header_and_sidecar_fields( + &sealed_block, + sidecar.canyon(), + self.is_cancun_active_at_timestamp(sealed_block.timestamp), + )?; + + prague::ensure_well_formed_fields( + sealed_block.body(), + sidecar.isthmus(), + self.is_prague_active_at_timestamp(sealed_block.timestamp), + )?; + + Ok(sealed_block) + } +}