From ffbdd97592b369e40df13fcc051c8b084f222edb Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 14 May 2025 18:21:45 +0200 Subject: [PATCH] feat(engine): add conversions for `ExecutionPayloadEnvelopeV5` (#16218) --- Cargo.lock | 1 + bin/reth-bench/src/valid_payload.rs | 2 +- crates/engine/primitives/src/lib.rs | 11 +- crates/ethereum/engine-primitives/Cargo.toml | 2 + .../ethereum/engine-primitives/src/error.rs | 13 ++ crates/ethereum/engine-primitives/src/lib.rs | 11 +- .../ethereum/engine-primitives/src/payload.rs | 186 ++++++++++++++---- crates/ethereum/payload/src/lib.rs | 21 +- crates/optimism/node/src/engine.rs | 6 +- crates/payload/primitives/src/lib.rs | 18 +- examples/custom-engine-types/src/main.rs | 5 +- 11 files changed, 211 insertions(+), 65 deletions(-) create mode 100644 crates/ethereum/engine-primitives/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index ee5abf18ce..6ea76d3cc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8097,6 +8097,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", + "thiserror 2.0.12", ] [[package]] diff --git a/bin/reth-bench/src/valid_payload.rs b/bin/reth-bench/src/valid_payload.rs index 8a7021da7d..e0c3cbc514 100644 --- a/bin/reth-bench/src/valid_payload.rs +++ b/bin/reth-bench/src/valid_payload.rs @@ -345,7 +345,7 @@ pub(crate) async fn call_forkchoice_updated>( payload_attributes: Option, ) -> TransportResult { match message_version { - EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { + EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => { provider.fork_choice_updated_v3_wait(forkchoice_state, payload_attributes).await } EngineApiMessageVersion::V2 => { diff --git a/crates/engine/primitives/src/lib.rs b/crates/engine/primitives/src/lib.rs index a30d41ae38..b9ac213e5d 100644 --- a/crates/engine/primitives/src/lib.rs +++ b/crates/engine/primitives/src/lib.rs @@ -57,7 +57,8 @@ pub trait EngineTypes: BuiltPayload: TryInto + TryInto + TryInto - + TryInto, + + TryInto + + TryInto, > + DeserializeOwned + Serialize { @@ -93,6 +94,14 @@ pub trait EngineTypes: + Send + Sync + 'static; + /// Execution Payload V5 envelope type. + type ExecutionPayloadEnvelopeV5: DeserializeOwned + + Serialize + + Clone + + Unpin + + Send + + Sync + + 'static; } /// Type that validates an [`ExecutionPayload`]. diff --git a/crates/ethereum/engine-primitives/Cargo.toml b/crates/ethereum/engine-primitives/Cargo.toml index 75e2af0237..db42e79aa7 100644 --- a/crates/ethereum/engine-primitives/Cargo.toml +++ b/crates/ethereum/engine-primitives/Cargo.toml @@ -26,6 +26,7 @@ alloy-rlp.workspace = true # misc serde.workspace = true sha2.workspace = true +thiserror.workspace = true [dev-dependencies] serde_json.workspace = true @@ -41,6 +42,7 @@ std = [ "serde/std", "sha2/std", "serde_json/std", + "thiserror/std", "reth-engine-primitives/std", "reth-primitives-traits/std", ] diff --git a/crates/ethereum/engine-primitives/src/error.rs b/crates/ethereum/engine-primitives/src/error.rs new file mode 100644 index 0000000000..917e0a74b5 --- /dev/null +++ b/crates/ethereum/engine-primitives/src/error.rs @@ -0,0 +1,13 @@ +use thiserror::Error; + +/// Error during [`EthBuiltPayload`](crate::EthBuiltPayload) to execution payload envelope +/// conversion. +#[derive(Error, Debug)] +pub enum BuiltPayloadConversionError { + /// Unexpected EIP-4844 sidecars in the built payload. + #[error("unexpected EIP-4844 sidecars")] + UnexpectedEip4844Sidecars, + /// Unexpected EIP-7594 sidecars in the built payload. + #[error("unexpected EIP-7594 sidecars")] + UnexpectedEip7594Sidecars, +} diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index 80f0bf3937..ceb3a3b0a8 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -12,9 +12,12 @@ extern crate alloc; mod payload; -pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes}; +pub use payload::{BlobSidecars, EthBuiltPayload, EthPayloadBuilderAttributes}; -use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload}; +mod error; +pub use error::*; + +use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5}; pub use alloy_rpc_types_engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes, @@ -62,12 +65,14 @@ where + TryInto + TryInto + TryInto - + TryInto, + + TryInto + + TryInto, { type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1; type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadEnvelopeV3 = ExecutionPayloadEnvelopeV3; type ExecutionPayloadEnvelopeV4 = ExecutionPayloadEnvelopeV4; + type ExecutionPayloadEnvelopeV5 = ExecutionPayloadEnvelopeV5; } /// A default payload type for [`EthEngineTypes`] diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index d43c43abc1..ea5d2bfa62 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -1,18 +1,24 @@ //! Contains types required for building a payload. use alloc::{sync::Arc, vec::Vec}; -use alloy_eips::{eip4844::BlobTransactionSidecar, eip4895::Withdrawals, eip7685::Requests}; +use alloy_eips::{ + eip4844::BlobTransactionSidecar, eip4895::Withdrawals, eip7594::BlobTransactionSidecarEip7594, + eip7685::Requests, +}; use alloy_primitives::{Address, B256, U256}; use alloy_rlp::Encodable; use alloy_rpc_types_engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, - ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId, + BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, + ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadFieldV2, + ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId, }; use core::convert::Infallible; use reth_ethereum_primitives::{Block, EthPrimitives}; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives_traits::SealedBlock; +use crate::BuiltPayloadConversionError; + /// Contains the built payload. /// /// According to the [engine API specification](https://github.com/ethereum/execution-apis/blob/main/src/engine/README.md) the execution layer should build the initial version of the payload with an empty transaction set and then keep update it in order to maximize the revenue. @@ -28,7 +34,7 @@ pub struct EthBuiltPayload { pub(crate) fees: U256, /// The blobs, proofs, and commitments in the block. If the block is pre-cancun, this will be /// empty. - pub(crate) sidecars: Vec, + pub(crate) sidecars: BlobSidecars, /// The requests of the payload pub(crate) requests: Option, } @@ -38,14 +44,14 @@ pub struct EthBuiltPayload { impl EthBuiltPayload { /// Initializes the payload with the given initial block /// - /// Caution: This does not set any [`BlobTransactionSidecar`]. + /// Caution: This does not set any [`BlobSidecars`]. pub const fn new( id: PayloadId, block: Arc>, fees: U256, requests: Option, ) -> Self { - Self { id, block, fees, sidecars: Vec::new(), requests } + Self { id, block, fees, requests, sidecars: BlobSidecars::Empty } } /// Returns the identifier of the payload. @@ -64,22 +70,89 @@ impl EthBuiltPayload { } /// Returns the blob sidecars. - pub fn sidecars(&self) -> &[BlobTransactionSidecar] { + pub const fn sidecars(&self) -> &BlobSidecars { &self.sidecars } - /// Adds sidecars to the payload. - pub fn extend_sidecars(&mut self, sidecars: impl IntoIterator) { - self.sidecars.extend(sidecars) + /// Sets blob transactions sidecars on the payload. + pub fn with_sidecars(mut self, sidecars: impl Into) -> Self { + self.sidecars = sidecars.into(); + self } - /// Same as [`Self::extend_sidecars`] but returns the type again. - pub fn with_sidecars( - mut self, - sidecars: impl IntoIterator, - ) -> Self { - self.extend_sidecars(sidecars); - self + /// Try converting built payload into [`ExecutionPayloadEnvelopeV3`]. + /// + /// Returns an error if the payload contains non EIP-4844 sidecar. + pub fn try_into_v3(self) -> Result { + let Self { block, fees, sidecars, .. } = self; + + let blobs_bundle = match sidecars { + BlobSidecars::Empty => BlobsBundleV1::empty(), + BlobSidecars::Eip4844(sidecars) => BlobsBundleV1::from(sidecars), + BlobSidecars::Eip7594(_) => { + return Err(BuiltPayloadConversionError::UnexpectedEip7594Sidecars) + } + }; + + Ok(ExecutionPayloadEnvelopeV3 { + execution_payload: ExecutionPayloadV3::from_block_unchecked( + block.hash(), + &Arc::unwrap_or_clone(block).into_block(), + ), + block_value: fees, + // From the engine API spec: + // + // > Client software **MAY** use any heuristics to decide whether to set + // `shouldOverrideBuilder` flag or not. If client software does not implement any + // heuristic this flag **SHOULD** be set to `false`. + // + // Spec: + // + should_override_builder: false, + blobs_bundle, + }) + } + + /// Try converting built payload into [`ExecutionPayloadEnvelopeV4`]. + /// + /// Returns an error if the payload contains non EIP-4844 sidecar. + pub fn try_into_v4(self) -> Result { + Ok(ExecutionPayloadEnvelopeV4 { + execution_requests: self.requests.clone().unwrap_or_default(), + envelope_inner: self.try_into()?, + }) + } + + /// Try converting built payload into [`ExecutionPayloadEnvelopeV5`]. + pub fn try_into_v5(self) -> Result { + let Self { block, fees, sidecars, requests, .. } = self; + + let blobs_bundle = match sidecars { + BlobSidecars::Empty => BlobsBundleV2::empty(), + BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars), + BlobSidecars::Eip4844(_) => { + return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars) + } + }; + + Ok(ExecutionPayloadEnvelopeV5 { + execution_payload: ExecutionPayloadV3::from_block_unchecked( + block.hash(), + &Arc::unwrap_or_clone(block).into_block(), + ), + block_value: fees, + // From the engine API spec: + // + // > Client software **MAY** use any heuristics to decide whether to set + // `shouldOverrideBuilder` flag or not. If client software does not implement any + // heuristic this flag **SHOULD** be set to `false`. + // + // Spec: + // + should_override_builder: false, + blobs_bundle, + execution_requests: requests.unwrap_or_default(), + }) } } @@ -124,36 +197,63 @@ impl From for ExecutionPayloadEnvelopeV2 { } } -impl From for ExecutionPayloadEnvelopeV3 { - fn from(value: EthBuiltPayload) -> Self { - let EthBuiltPayload { block, fees, sidecars, .. } = value; +impl TryFrom for ExecutionPayloadEnvelopeV3 { + type Error = BuiltPayloadConversionError; - Self { - execution_payload: ExecutionPayloadV3::from_block_unchecked( - block.hash(), - &Arc::unwrap_or_clone(block).into_block(), - ), - block_value: fees, - // From the engine API spec: - // - // > Client software **MAY** use any heuristics to decide whether to set - // `shouldOverrideBuilder` flag or not. If client software does not implement any - // heuristic this flag **SHOULD** be set to `false`. - // - // Spec: - // - should_override_builder: false, - blobs_bundle: sidecars.into(), - } + fn try_from(value: EthBuiltPayload) -> Result { + value.try_into_v3() } } -impl From for ExecutionPayloadEnvelopeV4 { - fn from(value: EthBuiltPayload) -> Self { - Self { - execution_requests: value.requests.clone().unwrap_or_default(), - envelope_inner: value.into(), - } +impl TryFrom for ExecutionPayloadEnvelopeV4 { + type Error = BuiltPayloadConversionError; + + fn try_from(value: EthBuiltPayload) -> Result { + value.try_into_v4() + } +} + +impl TryFrom for ExecutionPayloadEnvelopeV5 { + type Error = BuiltPayloadConversionError; + + fn try_from(value: EthBuiltPayload) -> Result { + value.try_into_v5() + } +} + +/// An enum representing blob transaction sidecars belonging to [`EthBuiltPayload`]. +#[derive(Clone, Default, Debug)] +pub enum BlobSidecars { + /// No sidecars (default). + #[default] + Empty, + /// EIP-4844 style sidecars. + Eip4844(Vec), + /// EIP-7594 style sidecars. + Eip7594(Vec), +} + +impl BlobSidecars { + /// Create new EIP-4844 style sidecars. + pub const fn eip4844(sidecars: Vec) -> Self { + Self::Eip4844(sidecars) + } + + /// Create new EIP-7594 style sidecars. + pub const fn eip7594(sidecars: Vec) -> Self { + Self::Eip7594(sidecars) + } +} + +impl From> for BlobSidecars { + fn from(value: Vec) -> Self { + Self::eip4844(value) + } +} + +impl From> for BlobSidecars { + fn from(value: Vec) -> Self { + Self::eip7594(value) } } diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index cf5ec60068..1fb805090c 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -9,9 +9,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![allow(clippy::useless_let_if_seq)] -pub mod validator; -pub use validator::EthereumExecutionPayloadValidator; - use alloy_consensus::{Transaction, Typed2718}; use alloy_primitives::U256; use reth_basic_payload_builder::{ @@ -29,11 +26,13 @@ use reth_evm_ethereum::EthEvmConfig; use reth_payload_builder::{EthBuiltPayload, EthPayloadBuilderAttributes}; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; +use reth_primitives_traits::transaction::error::InvalidTransactionError; use reth_revm::{database::StateProviderDatabase, db::State}; use reth_storage_api::StateProviderFactory; use reth_transaction_pool::{ - error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes, - PoolTransaction, TransactionPool, ValidPoolTransaction, + error::{Eip4844PoolTransactionError, InvalidPoolTransactionError}, + BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, + ValidPoolTransaction, }; use revm::context_interface::Block as _; use std::sync::Arc; @@ -41,8 +40,9 @@ use tracing::{debug, trace, warn}; mod config; pub use config::*; -use reth_primitives_traits::transaction::error::InvalidTransactionError; -use reth_transaction_pool::error::Eip4844PoolTransactionError; + +pub mod validator; +pub use validator::EthereumExecutionPayloadValidator; type BestTransactionsIter = Box< dyn BestTransactions::Transaction>>>, @@ -315,10 +315,9 @@ where let sealed_block = Arc::new(block.sealed_block().clone()); debug!(target: "payload_builder", id=%attributes.id, sealed_block_header = ?sealed_block.sealed_header(), "sealed built block"); - let mut payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests); - - // extend the payload with the blob sidecars from the executed txs - payload.extend_sidecars(blob_sidecars.into_iter().map(Arc::unwrap_or_clone)); + let payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests) + // add blob sidecars from the executed txs + .with_sidecars(blob_sidecars.into_iter().map(Arc::unwrap_or_clone).collect::>()); Ok(BuildOutcome::Better { payload, cached_reads }) } diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index ec1fa385cc..cfd9aa8e6a 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -66,6 +66,7 @@ where type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadEnvelopeV3 = OpExecutionPayloadEnvelopeV3; type ExecutionPayloadEnvelopeV4 = OpExecutionPayloadEnvelopeV4; + type ExecutionPayloadEnvelopeV5 = OpExecutionPayloadEnvelopeV4; } /// Validator for Optimism engine API. @@ -234,7 +235,10 @@ pub fn validate_withdrawals_presence( .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai)) } } - EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { + EngineApiMessageVersion::V2 | + EngineApiMessageVersion::V3 | + EngineApiMessageVersion::V4 | + EngineApiMessageVersion::V5 => { if is_shanghai && !has_withdrawals { return Err(message_validation_kind .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai)) diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index 561aeea415..ed9c73dedd 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -155,7 +155,10 @@ pub fn validate_withdrawals_presence( .to_error(VersionSpecificValidationError::WithdrawalsNotSupportedInV1)) } } - EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { + EngineApiMessageVersion::V2 | + EngineApiMessageVersion::V3 | + EngineApiMessageVersion::V4 | + EngineApiMessageVersion::V5 => { if is_shanghai_active && !has_withdrawals { return Err(message_validation_kind .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai)) @@ -256,7 +259,7 @@ pub fn validate_parent_beacon_block_root_presence( )) } } - EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { + EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => { if !has_parent_beacon_block_root { return Err(validation_kind .to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun)) @@ -350,12 +353,16 @@ pub enum EngineApiMessageVersion { /// Version 3 /// /// Added in the Cancun hardfork. - #[default] V3 = 3, /// Version 4 /// /// Added in the Prague hardfork. + #[default] V4 = 4, + /// Version 5 + /// + /// Added in the Osaka hardfork. + V5 = 5, } impl EngineApiMessageVersion { @@ -378,6 +385,11 @@ impl EngineApiMessageVersion { pub const fn is_v4(&self) -> bool { matches!(self, Self::V4) } + + /// Returns true if the version is V5. + pub const fn is_v5(&self) -> bool { + matches!(self, Self::V5) + } } /// Determines how we should choose the payload to return. diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index 4f146073ef..2d8b784c71 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -23,8 +23,8 @@ use alloy_primitives::{Address, B256}; use alloy_rpc_types::{ engine::{ ExecutionData, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, - ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes, - PayloadId, + ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadV1, + PayloadAttributes as EthPayloadAttributes, PayloadId, }, Withdrawal, }; @@ -176,6 +176,7 @@ impl EngineTypes for CustomEngineTypes { type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadEnvelopeV3 = ExecutionPayloadEnvelopeV3; type ExecutionPayloadEnvelopeV4 = ExecutionPayloadEnvelopeV4; + type ExecutionPayloadEnvelopeV5 = ExecutionPayloadEnvelopeV5; } /// Custom engine validator