From c362fc6c36d87a999251f3e9a42bfa31688a5217 Mon Sep 17 00:00:00 2001 From: jn Date: Thu, 6 Jun 2024 16:19:21 -0700 Subject: [PATCH] feat: Introduce payload primitives (#8642) Co-authored-by: Matthias Seitz --- Cargo.lock | 26 +- Cargo.toml | 4 +- bin/reth/Cargo.toml | 1 + bin/reth/src/lib.rs | 1 + crates/consensus/beacon/Cargo.toml | 1 + crates/consensus/beacon/src/engine/mod.rs | 3 +- crates/engine-primitives/Cargo.toml | 5 +- crates/engine-primitives/src/lib.rs | 328 +---------------- crates/ethereum/engine-primitives/Cargo.toml | 1 + crates/ethereum/engine-primitives/src/lib.rs | 5 +- .../ethereum/engine-primitives/src/payload.rs | 2 +- crates/node/api/Cargo.toml | 1 + crates/node/api/src/lib.rs | 4 + crates/optimism/node/src/engine.rs | 9 +- crates/optimism/payload/Cargo.toml | 2 +- crates/optimism/payload/src/payload.rs | 2 +- crates/payload/basic/Cargo.toml | 2 +- crates/payload/basic/src/lib.rs | 2 +- crates/payload/builder/Cargo.toml | 1 + crates/payload/builder/src/noop.rs | 3 +- crates/payload/builder/src/service.rs | 3 +- crates/payload/builder/src/traits.rs | 2 +- crates/payload/primitives/Cargo.toml | 26 ++ crates/payload/primitives/src/error.rs | 117 ++++++ crates/payload/primitives/src/lib.rs | 333 ++++++++++++++++++ .../primitives}/src/payload.rs | 4 +- .../primitives}/src/traits.rs | 7 +- crates/rpc/rpc-engine-api/Cargo.toml | 1 + crates/rpc/rpc-engine-api/src/engine_api.rs | 9 +- crates/rpc/rpc-engine-api/src/error.rs | 2 +- examples/custom-engine-types/src/main.rs | 12 +- 31 files changed, 558 insertions(+), 361 deletions(-) create mode 100644 crates/payload/primitives/Cargo.toml create mode 100644 crates/payload/primitives/src/error.rs create mode 100644 crates/payload/primitives/src/lib.rs rename crates/{engine-primitives => payload/primitives}/src/payload.rs (97%) rename crates/{engine-primitives => payload/primitives}/src/traits.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index c5e982dd2..20af7e060 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6206,6 +6206,7 @@ dependencies = [ "reth-node-optimism", "reth-optimism-primitives", "reth-payload-builder", + "reth-payload-primitives", "reth-payload-validator", "reth-primitives", "reth-provider", @@ -6263,9 +6264,9 @@ dependencies = [ "futures-core", "futures-util", "metrics", - "reth-engine-primitives", "reth-metrics", "reth-payload-builder", + "reth-payload-primitives", "reth-primitives", "reth-provider", "reth-revm", @@ -6300,6 +6301,7 @@ dependencies = [ "reth-metrics", "reth-network-p2p", "reth-payload-builder", + "reth-payload-primitives", "reth-payload-validator", "reth-primitives", "reth-provider", @@ -6686,10 +6688,9 @@ dependencies = [ name = "reth-engine-primitives" version = "0.2.0-beta.9" dependencies = [ + "reth-payload-primitives", "reth-primitives", - "reth-rpc-types", "serde", - "thiserror", ] [[package]] @@ -6775,6 +6776,7 @@ version = "0.2.0-beta.9" dependencies = [ "alloy-rlp", "reth-engine-primitives", + "reth-payload-primitives", "reth-primitives", "reth-rpc-types", "reth-rpc-types-compat", @@ -7180,6 +7182,7 @@ dependencies = [ "reth-evm", "reth-network", "reth-payload-builder", + "reth-payload-primitives", "reth-provider", "reth-tasks", "reth-transaction-pool", @@ -7396,10 +7399,10 @@ version = "0.2.0-beta.9" dependencies = [ "alloy-rlp", "reth-basic-payload-builder", - "reth-engine-primitives", "reth-evm", "reth-evm-optimism", "reth-payload-builder", + "reth-payload-primitives", "reth-primitives", "reth-provider", "reth-revm", @@ -7426,6 +7429,7 @@ dependencies = [ "reth-errors", "reth-ethereum-engine-primitives", "reth-metrics", + "reth-payload-primitives", "reth-primitives", "reth-provider", "reth-rpc-types", @@ -7438,6 +7442,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-payload-primitives" +version = "0.2.0-beta.9" +dependencies = [ + "reth-errors", + "reth-primitives", + "reth-rpc-types", + "reth-transaction-pool", + "serde", + "thiserror", + "tokio", +] + [[package]] name = "reth-payload-validator" version = "0.2.0-beta.9" @@ -7715,6 +7732,7 @@ dependencies = [ "reth-evm", "reth-metrics", "reth-payload-builder", + "reth-payload-primitives", "reth-primitives", "reth-provider", "reth-rpc-api", diff --git a/Cargo.toml b/Cargo.toml index 89be7d481..496e999f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "crates/payload/basic/", "crates/payload/builder/", "crates/payload/ethereum/", + "crates/payload/primitives/", "crates/payload/validator/", "crates/primitives/", "crates/prune/", @@ -104,7 +105,7 @@ members = [ "examples/rpc-db/", "examples/txpool-tracing/", "testing/ef-tests/", - "testing/testing-utils", + "testing/testing-utils" ] default-members = ["bin/reth"] @@ -290,6 +291,7 @@ reth-optimism-consensus = { path = "crates/optimism/consensus" } reth-optimism-payload-builder = { path = "crates/optimism/payload" } reth-optimism-primitives = { path = "crates/optimism/primitives" } reth-payload-builder = { path = "crates/payload/builder" } +reth-payload-primitives = { path = "crates/payload/primitives" } reth-payload-validator = { path = "crates/payload/validator" } reth-primitives = { path = "crates/primitives" } reth-provider = { path = "crates/storage/provider" } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 0c1560d82..6d003cf09 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -44,6 +44,7 @@ reth-tracing.workspace = true reth-tasks.workspace = true reth-ethereum-payload-builder.workspace = true reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true reth-payload-validator.workspace = true reth-basic-payload-builder.workspace = true reth-discv4.workspace = true diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 9dd43bcd2..7c024438a 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -37,6 +37,7 @@ pub mod utils; /// Re-exported payload related types pub mod payload { pub use reth_payload_builder::*; + pub use reth_payload_primitives::*; pub use reth_payload_validator::ExecutionPayloadValidator; } diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index cd240a57e..3f6043807 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -22,6 +22,7 @@ reth-provider.workspace = true reth-rpc-types.workspace = true reth-tasks.workspace = true reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true reth-payload-validator.workspace = true reth-prune.workspace = true reth-static-file.workspace = true diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index de79ee528..5a934144d 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -5,7 +5,7 @@ use reth_blockchain_tree_api::{ BlockStatus, BlockValidationKind, BlockchainTreeEngine, CanonicalOutcome, InsertPayloadOk, }; use reth_db_api::database::Database; -use reth_engine_primitives::{EngineTypes, PayloadAttributes, PayloadBuilderAttributes}; +use reth_engine_primitives::EngineTypes; use reth_errors::{BlockValidationError, ProviderResult, RethError, RethResult}; use reth_network_p2p::{ bodies::client::BodiesClient, @@ -13,6 +13,7 @@ use reth_network_p2p::{ sync::{NetworkSyncUpdater, SyncState}, }; use reth_payload_builder::PayloadBuilderHandle; +use reth_payload_primitives::{PayloadAttributes, PayloadBuilderAttributes}; use reth_payload_validator::ExecutionPayloadValidator; use reth_primitives::{ constants::EPOCH_SLOTS, diff --git a/crates/engine-primitives/Cargo.toml b/crates/engine-primitives/Cargo.toml index 86f208774..46da7286d 100644 --- a/crates/engine-primitives/Cargo.toml +++ b/crates/engine-primitives/Cargo.toml @@ -13,8 +13,7 @@ workspace = true [dependencies] # reth reth-primitives.workspace = true -reth-rpc-types.workspace = true +reth-payload-primitives.workspace = true # misc -serde.workspace = true -thiserror.workspace = true +serde.workspace = true \ No newline at end of file diff --git a/crates/engine-primitives/src/lib.rs b/crates/engine-primitives/src/lib.rs index 646ce1193..c10156fb7 100644 --- a/crates/engine-primitives/src/lib.rs +++ b/crates/engine-primitives/src/lib.rs @@ -11,20 +11,12 @@ use core::fmt; use reth_primitives::ChainSpec; -/// Contains traits to abstract over payload attributes types and default implementations of the -/// [`PayloadAttributes`] trait for ethereum mainnet and optimism types. -pub mod traits; +use reth_payload_primitives::{ + BuiltPayload, EngineApiMessageVersion, EngineObjectValidationError, PayloadAttributes, + PayloadBuilderAttributes, PayloadOrAttributes, +}; + use serde::{de::DeserializeOwned, ser::Serialize}; -pub use traits::{BuiltPayload, PayloadAttributes, PayloadBuilderAttributes}; - -/// Contains error types used in the traits defined in this crate. -pub mod error; -pub use error::{EngineObjectValidationError, VersionSpecificValidationError}; - -/// Contains types used in implementations of the [`PayloadAttributes`] trait. -pub mod payload; -pub use payload::PayloadOrAttributes; - /// The types that are used by the engine API. pub trait EngineTypes: DeserializeOwned + Serialize + fmt::Debug + Unpin + Send + Sync + Clone @@ -63,313 +55,3 @@ pub trait EngineTypes: payload_or_attrs: PayloadOrAttributes<'_, Self::PayloadAttributes>, ) -> Result<(), EngineObjectValidationError>; } - -/// Validates the timestamp depending on the version called: -/// -/// * If V2, this ensures that the payload timestamp is pre-Cancun. -/// * If V3, this ensures that the payload timestamp is within the Cancun timestamp. -/// * If V4, this ensures that the payload timestamp is within the Prague timestamp. -/// -/// Otherwise, this will return [`EngineObjectValidationError::UnsupportedFork`]. -pub fn validate_payload_timestamp( - chain_spec: &ChainSpec, - version: EngineApiMessageVersion, - timestamp: u64, -) -> Result<(), EngineObjectValidationError> { - let is_cancun = chain_spec.is_cancun_active_at_timestamp(timestamp); - if version == EngineApiMessageVersion::V2 && is_cancun { - // From the Engine API spec: - // - // ### Update the methods of previous forks - // - // This document defines how Cancun payload should be handled by the [`Shanghai - // API`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md). - // - // For the following methods: - // - // - [`engine_forkchoiceUpdatedV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_forkchoiceupdatedv2) - // - [`engine_newPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_newpayloadV2) - // - [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_getpayloadv2) - // - // a validation **MUST** be added: - // - // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of - // payload or payloadAttributes is greater or equal to the Cancun activation timestamp. - return Err(EngineObjectValidationError::UnsupportedFork) - } - - if version == EngineApiMessageVersion::V3 && !is_cancun { - // From the Engine API spec: - // - // - // For `engine_getPayloadV3`: - // - // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of - // the built payload does not fall within the time frame of the Cancun fork. - // - // For `engine_forkchoiceUpdatedV3`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the - // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within - // the time frame of the Cancun fork. - // - // For `engine_newPayloadV3`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of - // the payload does not fall within the time frame of the Cancun fork. - return Err(EngineObjectValidationError::UnsupportedFork) - } - - let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp); - if version == EngineApiMessageVersion::V4 && !is_prague { - // From the Engine API spec: - // - // - // For `engine_getPayloadV4`: - // - // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of - // the built payload does not fall within the time frame of the Prague fork. - // - // For `engine_forkchoiceUpdatedV4`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the - // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within - // the time frame of the Prague fork. - // - // For `engine_newPayloadV4`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of - // the payload does not fall within the time frame of the Prague fork. - return Err(EngineObjectValidationError::UnsupportedFork) - } - Ok(()) -} - -/// Validates the presence of the `withdrawals` field according to the payload timestamp. -/// After Shanghai, withdrawals field must be [Some]. -/// Before Shanghai, withdrawals field must be [None]; -pub fn validate_withdrawals_presence( - chain_spec: &ChainSpec, - version: EngineApiMessageVersion, - message_validation_kind: MessageValidationKind, - timestamp: u64, - has_withdrawals: bool, -) -> Result<(), EngineObjectValidationError> { - let is_shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp); - - match version { - EngineApiMessageVersion::V1 => { - if has_withdrawals { - return Err(message_validation_kind - .to_error(VersionSpecificValidationError::WithdrawalsNotSupportedInV1)) - } - } - EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { - if is_shanghai_active && !has_withdrawals { - return Err(message_validation_kind - .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai)) - } - if !is_shanghai_active && has_withdrawals { - return Err(message_validation_kind - .to_error(VersionSpecificValidationError::HasWithdrawalsPreShanghai)) - } - } - }; - - Ok(()) -} - -/// Validate the presence of the `parentBeaconBlockRoot` field according to the given timestamp. -/// This method is meant to be used with either a `payloadAttributes` field or a full payload, with -/// the `engine_forkchoiceUpdated` and `engine_newPayload` methods respectively. -/// -/// After Cancun, the `parentBeaconBlockRoot` field must be [Some]. -/// Before Cancun, the `parentBeaconBlockRoot` field must be [None]. -/// -/// If the engine API message version is V1 or V2, and the timestamp is post-Cancun, then this will -/// return [`EngineObjectValidationError::UnsupportedFork`]. -/// -/// If the timestamp is before the Cancun fork and the engine API message version is V3, then this -/// will return [`EngineObjectValidationError::UnsupportedFork`]. -/// -/// If the engine API message version is V3, but the `parentBeaconBlockRoot` is [None], then -/// this will return [`VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun`]. -/// -/// This implements the following Engine API spec rules: -/// -/// 1. Client software **MUST** check that provided set of parameters and their fields strictly -/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any -/// field having `null` value **MUST** be considered as not provided. -/// -/// For `engine_forkchoiceUpdatedV3`: -/// -/// 1. Client software **MUST** check that provided set of parameters and their fields strictly -/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any -/// field having `null` value **MUST** be considered as not provided. -/// -/// 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the following -/// sequence of checks that **MUST** be run over `payloadAttributes`: -/// 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003: Invalid -/// payload attributes` on failure. -/// 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return -/// `-38005: Unsupported fork` on failure. -/// 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by -/// `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on failure. -/// 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled -/// back. -/// -/// For `engine_newPayloadV3`: -/// -/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the -/// payload does not fall within the time frame of the Cancun fork. -/// -/// For `engine_newPayloadV4`: -/// -/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the -/// payload does not fall within the time frame of the Prague fork. -/// -/// Returning the right error code (ie, if the client should return `-38003: Invalid payload -/// attributes` is handled by the `message_validation_kind` parameter. If the parameter is -/// `MessageValidationKind::Payload`, then the error code will be `-32602: Invalid params`. If the -/// parameter is `MessageValidationKind::PayloadAttributes`, then the error code will be `-38003: -/// Invalid payload attributes`. -pub fn validate_parent_beacon_block_root_presence( - chain_spec: &ChainSpec, - version: EngineApiMessageVersion, - validation_kind: MessageValidationKind, - timestamp: u64, - has_parent_beacon_block_root: bool, -) -> Result<(), EngineObjectValidationError> { - // 1. Client software **MUST** check that provided set of parameters and their fields strictly - // matches the expected one and return `-32602: Invalid params` error if this check fails. - // Any field having `null` value **MUST** be considered as not provided. - // - // For `engine_forkchoiceUpdatedV3`: - // - // 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the - // following sequence of checks that **MUST** be run over `payloadAttributes`: - // 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003: - // Invalid payload attributes` on failure. - // 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return - // `-38005: Unsupported fork` on failure. - // 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by - // `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on - // failure. - // 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled - // back. - match version { - EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 => { - if has_parent_beacon_block_root { - return Err(validation_kind.to_error( - VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3, - )) - } - } - EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { - if !has_parent_beacon_block_root { - return Err(validation_kind - .to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun)) - } - } - }; - - // For `engine_forkchoiceUpdatedV3`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the - // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within the - // time frame of the Cancun fork. - // - // For `engine_newPayloadV3`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the - // payload does not fall within the time frame of the Cancun fork. - validate_payload_timestamp(chain_spec, version, timestamp)?; - - Ok(()) -} - -/// A type that represents whether or not we are validating a payload or payload attributes. -/// -/// This is used to ensure that the correct error code is returned when validating the payload or -/// payload attributes. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MessageValidationKind { - /// We are validating fields of a payload attributes. - PayloadAttributes, - /// We are validating fields of a payload. - Payload, -} - -impl MessageValidationKind { - /// Returns an `EngineObjectValidationError` based on the given - /// `VersionSpecificValidationError` and the current validation kind. - pub const fn to_error( - self, - error: VersionSpecificValidationError, - ) -> EngineObjectValidationError { - match self { - Self::Payload => EngineObjectValidationError::Payload(error), - Self::PayloadAttributes => EngineObjectValidationError::PayloadAttributes(error), - } - } -} - -/// Validates the presence or exclusion of fork-specific fields based on the ethereum execution -/// payload, or payload attributes, and the message version. -/// -/// The object being validated is provided by the [`PayloadOrAttributes`] argument, which can be -/// either an execution payload, or payload attributes. -/// -/// The version is provided by the [`EngineApiMessageVersion`] argument. -pub fn validate_version_specific_fields( - chain_spec: &ChainSpec, - version: EngineApiMessageVersion, - payload_or_attrs: PayloadOrAttributes<'_, Type>, -) -> Result<(), EngineObjectValidationError> -where - Type: PayloadAttributes, -{ - validate_withdrawals_presence( - chain_spec, - version, - payload_or_attrs.message_validation_kind(), - payload_or_attrs.timestamp(), - payload_or_attrs.withdrawals().is_some(), - )?; - validate_parent_beacon_block_root_presence( - chain_spec, - version, - payload_or_attrs.message_validation_kind(), - payload_or_attrs.timestamp(), - payload_or_attrs.parent_beacon_block_root().is_some(), - ) -} - -/// The version of Engine API message. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum EngineApiMessageVersion { - /// Version 1 - V1, - /// Version 2 - /// - /// Added in the Shanghai hardfork. - V2, - /// Version 3 - /// - /// Added in the Cancun hardfork. - V3, - /// Version 4 - /// - /// Added in the Prague hardfork. - V4, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn version_ord() { - assert!(EngineApiMessageVersion::V4 > EngineApiMessageVersion::V3); - } -} diff --git a/crates/ethereum/engine-primitives/Cargo.toml b/crates/ethereum/engine-primitives/Cargo.toml index e38488413..c91e2086e 100644 --- a/crates/ethereum/engine-primitives/Cargo.toml +++ b/crates/ethereum/engine-primitives/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # reth reth-primitives.workspace = true reth-engine-primitives.workspace = true +reth-payload-primitives.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true revm-primitives.workspace = true diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index 4cfdf70a3..441f83f9a 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -11,9 +11,10 @@ mod payload; pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes}; -use reth_engine_primitives::{ +use reth_engine_primitives::EngineTypes; +use reth_payload_primitives::{ validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, - EngineTypes, PayloadOrAttributes, + PayloadOrAttributes, }; use reth_primitives::ChainSpec; use reth_rpc_types::{ diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index e059c8ae9..a80262200 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -1,7 +1,7 @@ //! Contains types required for building a payload. use alloy_rlp::Encodable; -use reth_engine_primitives::{BuiltPayload, PayloadBuilderAttributes}; +use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ constants::EIP1559_INITIAL_BASE_FEE, revm::config::revm_spec_by_timestamp_after_merge, Address, BlobTransactionSidecar, ChainSpec, Hardfork, Header, SealedBlock, Withdrawals, B256, U256, diff --git a/crates/node/api/Cargo.toml b/crates/node/api/Cargo.toml index d62368e95..2ae8f52c9 100644 --- a/crates/node/api/Cargo.toml +++ b/crates/node/api/Cargo.toml @@ -19,4 +19,5 @@ reth-engine-primitives.workspace = true reth-transaction-pool.workspace = true reth-network.workspace = true reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true reth-tasks.workspace = true diff --git a/crates/node/api/src/lib.rs b/crates/node/api/src/lib.rs index c1088f242..b01a2def0 100644 --- a/crates/node/api/src/lib.rs +++ b/crates/node/api/src/lib.rs @@ -12,6 +12,10 @@ pub use reth_engine_primitives as engine; pub use reth_engine_primitives::*; +/// Traits and helper types used to abstract over payload types. +pub use reth_payload_primitives as payload; +pub use reth_payload_primitives::*; + /// Traits and helper types used to abstract over EVM methods and types. pub use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index a01a84d3c..8321b9777 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -1,7 +1,10 @@ use reth_node_api::{ - engine::validate_parent_beacon_block_root_presence, EngineApiMessageVersion, - EngineObjectValidationError, EngineTypes, MessageValidationKind, PayloadOrAttributes, - VersionSpecificValidationError, + payload::{ + validate_parent_beacon_block_root_presence, EngineApiMessageVersion, + EngineObjectValidationError, MessageValidationKind, PayloadOrAttributes, + VersionSpecificValidationError, + }, + EngineTypes, }; use reth_optimism_payload_builder::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}; use reth_primitives::{ChainSpec, Hardfork}; diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index 567c02833..41024c612 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -19,10 +19,10 @@ reth-transaction-pool.workspace = true reth-provider.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true -reth-engine-primitives.workspace = true reth-evm.workspace = true reth-evm-optimism.workspace = true reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true reth-basic-payload-builder.workspace = true # ethereum diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index 7f42893c1..7f9bd1ce7 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -3,8 +3,8 @@ //! Optimism builder support use alloy_rlp::Encodable; -use reth_engine_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_payload_builder::EthPayloadBuilderAttributes; +use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ revm::config::revm_spec_by_timestamp_after_merge, revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}, diff --git a/crates/payload/basic/Cargo.toml b/crates/payload/basic/Cargo.toml index a19d32abb..68464d7f4 100644 --- a/crates/payload/basic/Cargo.toml +++ b/crates/payload/basic/Cargo.toml @@ -18,8 +18,8 @@ reth-revm.workspace = true reth-transaction-pool.workspace = true reth-provider.workspace = true reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true reth-tasks.workspace = true -reth-engine-primitives.workspace = true # ethereum alloy-rlp.workspace = true diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index 5eaf50194..3c60d27de 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -11,11 +11,11 @@ use crate::metrics::PayloadBuilderMetrics; use futures_core::ready; use futures_util::FutureExt; -use reth_engine_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_payload_builder::{ database::CachedReads, error::PayloadBuilderError, KeepPayloadJobAlive, PayloadId, PayloadJob, PayloadJobGenerator, }; +use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ constants::{EMPTY_WITHDRAWALS, RETH_CLIENT_VERSION, SLOT_DURATION}, proofs, BlockNumberOrTag, Bytes, ChainSpec, Request, SealedBlock, Withdrawals, B256, U256, diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index c95b22c3f..ce82ae7ff 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -19,6 +19,7 @@ reth-transaction-pool.workspace = true reth-errors.workspace = true reth-provider.workspace = true reth-engine-primitives.workspace = true +reth-payload-primitives.workspace = true reth-ethereum-engine-primitives.workspace = true # async diff --git a/crates/payload/builder/src/noop.rs b/crates/payload/builder/src/noop.rs index ccefa6777..ef919ecf7 100644 --- a/crates/payload/builder/src/noop.rs +++ b/crates/payload/builder/src/noop.rs @@ -2,7 +2,8 @@ use crate::{service::PayloadServiceCommand, PayloadBuilderHandle}; use futures_util::{ready, StreamExt}; -use reth_engine_primitives::{EngineTypes, PayloadBuilderAttributes}; +use reth_engine_primitives::EngineTypes; +use reth_payload_primitives::PayloadBuilderAttributes; use std::{ future::Future, pin::Pin, diff --git a/crates/payload/builder/src/service.rs b/crates/payload/builder/src/service.rs index f39037275..98790ef7d 100644 --- a/crates/payload/builder/src/service.rs +++ b/crates/payload/builder/src/service.rs @@ -11,7 +11,8 @@ use crate::{ KeepPayloadJobAlive, PayloadJob, }; use futures_util::{future::FutureExt, Stream, StreamExt}; -use reth_engine_primitives::{BuiltPayload, EngineTypes, PayloadBuilderAttributes}; +use reth_engine_primitives::EngineTypes; +use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_provider::CanonStateNotification; use reth_rpc_types::engine::PayloadId; use std::{ diff --git a/crates/payload/builder/src/traits.rs b/crates/payload/builder/src/traits.rs index cf747b0da..895670d8b 100644 --- a/crates/payload/builder/src/traits.rs +++ b/crates/payload/builder/src/traits.rs @@ -1,7 +1,7 @@ //! Trait abstractions used by the payload crate. use crate::error::PayloadBuilderError; -use reth_engine_primitives::{BuiltPayload, PayloadBuilderAttributes}; +use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_provider::CanonStateNotification; use std::future::Future; diff --git a/crates/payload/primitives/Cargo.toml b/crates/payload/primitives/Cargo.toml new file mode 100644 index 000000000..1cb992da2 --- /dev/null +++ b/crates/payload/primitives/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "reth-payload-primitives" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-errors.workspace = true +reth-primitives.workspace = true +reth-transaction-pool.workspace = true +reth-rpc-types.workspace = true + +# async +tokio = { workspace = true, features = ["sync"] } + +# misc +thiserror.workspace = true +serde.workspace = true \ No newline at end of file diff --git a/crates/payload/primitives/src/error.rs b/crates/payload/primitives/src/error.rs new file mode 100644 index 000000000..458dc4e30 --- /dev/null +++ b/crates/payload/primitives/src/error.rs @@ -0,0 +1,117 @@ +//! Error types emitted by types or implementations of this crate. + +use reth_errors::{ProviderError, RethError}; +use reth_primitives::{revm_primitives::EVMError, B256}; +use reth_transaction_pool::BlobStoreError; +use tokio::sync::oneshot; + +/// Possible error variants during payload building. +#[derive(Debug, thiserror::Error)] +pub enum PayloadBuilderError { + /// Thrown when the parent block is missing. + #[error("missing parent block {0}")] + MissingParentBlock(B256), + /// An oneshot channels has been closed. + #[error("sender has been dropped")] + ChannelClosed, + /// Error occurring in the blob store. + #[error(transparent)] + BlobStore(#[from] BlobStoreError), + /// Other internal error + #[error(transparent)] + Internal(#[from] RethError), + /// Unrecoverable error during evm execution. + #[error("evm execution error: {0}")] + EvmExecutionError(EVMError), + /// Thrown if the payload requests withdrawals before Shanghai activation. + #[error("withdrawals set before Shanghai activation")] + WithdrawalsBeforeShanghai, + /// Any other payload building errors. + #[error(transparent)] + Other(Box), +} + +impl PayloadBuilderError { + /// Create a new error from a boxed error. + pub fn other(error: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + Self::Other(Box::new(error)) + } +} + +impl From for PayloadBuilderError { + fn from(error: ProviderError) -> Self { + Self::Internal(RethError::Provider(error)) + } +} + +impl From for PayloadBuilderError { + fn from(_: oneshot::error::RecvError) -> Self { + Self::ChannelClosed + } +} + +/// Thrown when the payload or attributes are known to be invalid before processing. +/// +/// This is used mainly for +/// [`validate_version_specific_fields`](crate::validate_version_specific_fields), which validates +/// both execution payloads and forkchoice update attributes with respect to a method version. +#[derive(thiserror::Error, Debug)] +pub enum EngineObjectValidationError { + /// Thrown when the underlying validation error occurred while validating an + /// `ExecutionPayload`. + #[error("Payload validation error: {0}")] + Payload(VersionSpecificValidationError), + + /// Thrown when the underlying validation error occurred while validating a + /// `PayloadAttributes`. + #[error("Payload attributes validation error: {0}")] + PayloadAttributes(VersionSpecificValidationError), + + /// Thrown if `PayloadAttributes` or `ExecutionPayload` were provided with a timestamp, but the + /// version of the engine method called is meant for a fork that occurs after the provided + /// timestamp. + #[error("Unsupported fork")] + UnsupportedFork, + /// Another type of error that is not covered by the above variants. + #[error("Invalid params: {0}")] + InvalidParams(#[from] Box), +} + +/// Thrown when validating an execution payload OR payload attributes fails due to: +/// * The existence of a new field that is not supported in the given engine method version, or +/// * The absence of a field that is required in the given engine method version +#[derive(thiserror::Error, Debug)] +pub enum VersionSpecificValidationError { + /// Thrown if the pre-V3 `PayloadAttributes` or `ExecutionPayload` contains a parent beacon + /// block root + #[error("parent beacon block root not supported before V3")] + ParentBeaconBlockRootNotSupportedBeforeV3, + /// Thrown if `engine_forkchoiceUpdatedV1` or `engine_newPayloadV1` contains withdrawals + #[error("withdrawals not supported in V1")] + WithdrawalsNotSupportedInV1, + /// Thrown if `engine_forkchoiceUpdated` or `engine_newPayload` contains no withdrawals after + /// Shanghai + #[error("no withdrawals post-Shanghai")] + NoWithdrawalsPostShanghai, + /// Thrown if `engine_forkchoiceUpdated` or `engine_newPayload` contains withdrawals before + /// Shanghai + #[error("withdrawals pre-Shanghai")] + HasWithdrawalsPreShanghai, + /// Thrown if the `PayloadAttributes` or `ExecutionPayload` contains no parent beacon block + /// root after Cancun + #[error("no parent beacon block root post-cancun")] + NoParentBeaconBlockRootPostCancun, +} + +impl EngineObjectValidationError { + /// Creates an instance of the `InvalidParams` variant with the given error. + pub fn invalid_params(error: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + Self::InvalidParams(Box::new(error)) + } +} diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs new file mode 100644 index 000000000..2cc4fadf6 --- /dev/null +++ b/crates/payload/primitives/src/lib.rs @@ -0,0 +1,333 @@ +//! This crate defines abstractions to create and update payloads (blocks) + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +mod error; +pub use error::{EngineObjectValidationError, PayloadBuilderError, VersionSpecificValidationError}; + +/// Contains traits to abstract over payload attributes types and default implementations of the +/// [`PayloadAttributes`] trait for ethereum mainnet and optimism types. +mod traits; +pub use traits::{BuiltPayload, PayloadAttributes, PayloadBuilderAttributes}; + +mod payload; +pub use payload::PayloadOrAttributes; + +use reth_primitives::ChainSpec; +use std::fmt::Debug; + +/// Validates the timestamp depending on the version called: +/// +/// * If V2, this ensures that the payload timestamp is pre-Cancun. +/// * If V3, this ensures that the payload timestamp is within the Cancun timestamp. +/// * If V4, this ensures that the payload timestamp is within the Prague timestamp. +/// +/// Otherwise, this will return [`EngineObjectValidationError::UnsupportedFork`]. +pub fn validate_payload_timestamp( + chain_spec: &ChainSpec, + version: EngineApiMessageVersion, + timestamp: u64, +) -> Result<(), EngineObjectValidationError> { + let is_cancun = chain_spec.is_cancun_active_at_timestamp(timestamp); + if version == EngineApiMessageVersion::V2 && is_cancun { + // From the Engine API spec: + // + // ### Update the methods of previous forks + // + // This document defines how Cancun payload should be handled by the [`Shanghai + // API`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md). + // + // For the following methods: + // + // - [`engine_forkchoiceUpdatedV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_forkchoiceupdatedv2) + // - [`engine_newPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_newpayloadV2) + // - [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/ff43500e653abde45aec0f545564abfb648317af/src/engine/shanghai.md#engine_getpayloadv2) + // + // a validation **MUST** be added: + // + // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of + // payload or payloadAttributes is greater or equal to the Cancun activation timestamp. + return Err(EngineObjectValidationError::UnsupportedFork) + } + + if version == EngineApiMessageVersion::V3 && !is_cancun { + // From the Engine API spec: + // + // + // For `engine_getPayloadV3`: + // + // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of + // the built payload does not fall within the time frame of the Cancun fork. + // + // For `engine_forkchoiceUpdatedV3`: + // + // 2. Client software **MUST** return `-38005: Unsupported fork` error if the + // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within + // the time frame of the Cancun fork. + // + // For `engine_newPayloadV3`: + // + // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of + // the payload does not fall within the time frame of the Cancun fork. + return Err(EngineObjectValidationError::UnsupportedFork) + } + + let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp); + if version == EngineApiMessageVersion::V4 && !is_prague { + // From the Engine API spec: + // + // + // For `engine_getPayloadV4`: + // + // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of + // the built payload does not fall within the time frame of the Prague fork. + // + // For `engine_forkchoiceUpdatedV4`: + // + // 2. Client software **MUST** return `-38005: Unsupported fork` error if the + // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within + // the time frame of the Prague fork. + // + // For `engine_newPayloadV4`: + // + // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of + // the payload does not fall within the time frame of the Prague fork. + return Err(EngineObjectValidationError::UnsupportedFork) + } + Ok(()) +} + +/// Validates the presence of the `withdrawals` field according to the payload timestamp. +/// After Shanghai, withdrawals field must be [Some]. +/// Before Shanghai, withdrawals field must be [None]; +pub fn validate_withdrawals_presence( + chain_spec: &ChainSpec, + version: EngineApiMessageVersion, + message_validation_kind: MessageValidationKind, + timestamp: u64, + has_withdrawals: bool, +) -> Result<(), EngineObjectValidationError> { + let is_shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp); + + match version { + EngineApiMessageVersion::V1 => { + if has_withdrawals { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::WithdrawalsNotSupportedInV1)) + } + } + EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { + if is_shanghai_active && !has_withdrawals { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai)) + } + if !is_shanghai_active && has_withdrawals { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::HasWithdrawalsPreShanghai)) + } + } + }; + + Ok(()) +} + +/// Validate the presence of the `parentBeaconBlockRoot` field according to the given timestamp. +/// This method is meant to be used with either a `payloadAttributes` field or a full payload, with +/// the `engine_forkchoiceUpdated` and `engine_newPayload` methods respectively. +/// +/// After Cancun, the `parentBeaconBlockRoot` field must be [Some]. +/// Before Cancun, the `parentBeaconBlockRoot` field must be [None]. +/// +/// If the engine API message version is V1 or V2, and the timestamp is post-Cancun, then this will +/// return [`EngineObjectValidationError::UnsupportedFork`]. +/// +/// If the timestamp is before the Cancun fork and the engine API message version is V3, then this +/// will return [`EngineObjectValidationError::UnsupportedFork`]. +/// +/// If the engine API message version is V3, but the `parentBeaconBlockRoot` is [None], then +/// this will return [`VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun`]. +/// +/// This implements the following Engine API spec rules: +/// +/// 1. Client software **MUST** check that provided set of parameters and their fields strictly +/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any +/// field having `null` value **MUST** be considered as not provided. +/// +/// For `engine_forkchoiceUpdatedV3`: +/// +/// 1. Client software **MUST** check that provided set of parameters and their fields strictly +/// matches the expected one and return `-32602: Invalid params` error if this check fails. Any +/// field having `null` value **MUST** be considered as not provided. +/// +/// 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the following +/// sequence of checks that **MUST** be run over `payloadAttributes`: +/// 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003: Invalid +/// payload attributes` on failure. +/// 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return +/// `-38005: Unsupported fork` on failure. +/// 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by +/// `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on failure. +/// 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled +/// back. +/// +/// For `engine_newPayloadV3`: +/// +/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the +/// payload does not fall within the time frame of the Cancun fork. +/// +/// For `engine_newPayloadV4`: +/// +/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the +/// payload does not fall within the time frame of the Prague fork. +/// +/// Returning the right error code (ie, if the client should return `-38003: Invalid payload +/// attributes` is handled by the `message_validation_kind` parameter. If the parameter is +/// `MessageValidationKind::Payload`, then the error code will be `-32602: Invalid params`. If the +/// parameter is `MessageValidationKind::PayloadAttributes`, then the error code will be `-38003: +/// Invalid payload attributes`. +pub fn validate_parent_beacon_block_root_presence( + chain_spec: &ChainSpec, + version: EngineApiMessageVersion, + validation_kind: MessageValidationKind, + timestamp: u64, + has_parent_beacon_block_root: bool, +) -> Result<(), EngineObjectValidationError> { + // 1. Client software **MUST** check that provided set of parameters and their fields strictly + // matches the expected one and return `-32602: Invalid params` error if this check fails. + // Any field having `null` value **MUST** be considered as not provided. + // + // For `engine_forkchoiceUpdatedV3`: + // + // 2. Extend point (7) of the `engine_forkchoiceUpdatedV1` specification by defining the + // following sequence of checks that **MUST** be run over `payloadAttributes`: + // 1. `payloadAttributes` matches the `PayloadAttributesV3` structure, return `-38003: + // Invalid payload attributes` on failure. + // 2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return + // `-38005: Unsupported fork` on failure. + // 3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by + // `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on + // failure. + // 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled + // back. + match version { + EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 => { + if has_parent_beacon_block_root { + return Err(validation_kind.to_error( + VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3, + )) + } + } + EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { + if !has_parent_beacon_block_root { + return Err(validation_kind + .to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun)) + } + } + }; + + // For `engine_forkchoiceUpdatedV3`: + // + // 2. Client software **MUST** return `-38005: Unsupported fork` error if the + // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within the + // time frame of the Cancun fork. + // + // For `engine_newPayloadV3`: + // + // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the + // payload does not fall within the time frame of the Cancun fork. + validate_payload_timestamp(chain_spec, version, timestamp)?; + + Ok(()) +} + +/// A type that represents whether or not we are validating a payload or payload attributes. +/// +/// This is used to ensure that the correct error code is returned when validating the payload or +/// payload attributes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MessageValidationKind { + /// We are validating fields of a payload attributes. + PayloadAttributes, + /// We are validating fields of a payload. + Payload, +} + +impl MessageValidationKind { + /// Returns an `EngineObjectValidationError` based on the given + /// `VersionSpecificValidationError` and the current validation kind. + pub const fn to_error( + self, + error: VersionSpecificValidationError, + ) -> EngineObjectValidationError { + match self { + Self::Payload => EngineObjectValidationError::Payload(error), + Self::PayloadAttributes => EngineObjectValidationError::PayloadAttributes(error), + } + } +} + +/// Validates the presence or exclusion of fork-specific fields based on the ethereum execution +/// payload, or payload attributes, and the message version. +/// +/// The object being validated is provided by the [`PayloadOrAttributes`] argument, which can be +/// either an execution payload, or payload attributes. +/// +/// The version is provided by the [`EngineApiMessageVersion`] argument. +pub fn validate_version_specific_fields( + chain_spec: &ChainSpec, + version: EngineApiMessageVersion, + payload_or_attrs: PayloadOrAttributes<'_, Type>, +) -> Result<(), EngineObjectValidationError> +where + Type: PayloadAttributes, +{ + validate_withdrawals_presence( + chain_spec, + version, + payload_or_attrs.message_validation_kind(), + payload_or_attrs.timestamp(), + payload_or_attrs.withdrawals().is_some(), + )?; + validate_parent_beacon_block_root_presence( + chain_spec, + version, + payload_or_attrs.message_validation_kind(), + payload_or_attrs.timestamp(), + payload_or_attrs.parent_beacon_block_root().is_some(), + ) +} + +/// The version of Engine API message. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum EngineApiMessageVersion { + /// Version 1 + V1, + /// Version 2 + /// + /// Added in the Shanghai hardfork. + V2, + /// Version 3 + /// + /// Added in the Cancun hardfork. + V3, + /// Version 4 + /// + /// Added in the Prague hardfork. + V4, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn version_ord() { + assert!(EngineApiMessageVersion::V4 > EngineApiMessageVersion::V3); + } +} diff --git a/crates/engine-primitives/src/payload.rs b/crates/payload/primitives/src/payload.rs similarity index 97% rename from crates/engine-primitives/src/payload.rs rename to crates/payload/primitives/src/payload.rs index adb13eb1f..3bd53861b 100644 --- a/crates/engine-primitives/src/payload.rs +++ b/crates/payload/primitives/src/payload.rs @@ -1,9 +1,7 @@ -use crate::PayloadAttributes; +use crate::{MessageValidationKind, PayloadAttributes}; use reth_primitives::B256; use reth_rpc_types::engine::ExecutionPayload; -use super::MessageValidationKind; - /// Either an [`ExecutionPayload`] or a types that implements the [`PayloadAttributes`] trait. #[derive(Debug)] pub enum PayloadOrAttributes<'a, AttributesType> { diff --git a/crates/engine-primitives/src/traits.rs b/crates/payload/primitives/src/traits.rs similarity index 99% rename from crates/engine-primitives/src/traits.rs rename to crates/payload/primitives/src/traits.rs index 3966e619e..e1da29e8a 100644 --- a/crates/engine-primitives/src/traits.rs +++ b/crates/payload/primitives/src/traits.rs @@ -1,6 +1,3 @@ -use crate::{ - validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, -}; use reth_primitives::{ revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg}, Address, ChainSpec, Header, SealedBlock, Withdrawals, B256, U256, @@ -10,6 +7,10 @@ use reth_rpc_types::{ Withdrawal, }; +use crate::{ + validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, +}; + /// Represents a built payload type that contains a built [`SealedBlock`] and can be converted into /// engine API execution payloads. pub trait BuiltPayload: Send + Sync + std::fmt::Debug { diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index bd68a77d1..e18ecafe4 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -19,6 +19,7 @@ reth-rpc-types.workspace = true reth-storage-api.workspace = true reth-beacon-consensus.workspace = true reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true reth-tasks.workspace = true reth-rpc-types-compat.workspace = true reth-engine-primitives.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 1b9dc5316..9cc5ac79e 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -2,12 +2,13 @@ use crate::{metrics::EngineApiMetrics, EngineApiError, EngineApiResult}; use async_trait::async_trait; use jsonrpsee_core::RpcResult; use reth_beacon_consensus::BeaconConsensusEngineHandle; -use reth_engine_primitives::{ - validate_payload_timestamp, EngineApiMessageVersion, EngineTypes, PayloadAttributes, - PayloadBuilderAttributes, PayloadOrAttributes, -}; +use reth_engine_primitives::EngineTypes; use reth_evm::provider::EvmEnvProvider; use reth_payload_builder::PayloadStore; +use reth_payload_primitives::{ + validate_payload_timestamp, EngineApiMessageVersion, PayloadAttributes, + PayloadBuilderAttributes, PayloadOrAttributes, +}; use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Hardfork, B256, U64}; use reth_rpc_api::EngineApiServer; use reth_rpc_types::engine::{ diff --git a/crates/rpc/rpc-engine-api/src/error.rs b/crates/rpc/rpc-engine-api/src/error.rs index 6fc842f06..6fb7a1972 100644 --- a/crates/rpc/rpc-engine-api/src/error.rs +++ b/crates/rpc/rpc-engine-api/src/error.rs @@ -2,8 +2,8 @@ use jsonrpsee_types::error::{ INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, SERVER_ERROR_MSG, }; use reth_beacon_consensus::{BeaconForkChoiceUpdateError, BeaconOnNewPayloadError}; -use reth_engine_primitives::EngineObjectValidationError; use reth_payload_builder::error::PayloadBuilderError; +use reth_payload_primitives::EngineObjectValidationError; use reth_primitives::{B256, U256}; use reth_rpc_types::ToRpcError; use thiserror::Error; diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index ca7c00404..de6d38870 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -17,6 +17,11 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] +use std::convert::Infallible; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + use reth::{ builder::{ components::{ComponentsBuilder, PayloadServiceBuilder}, @@ -33,8 +38,8 @@ use reth_basic_payload_builder::{ PayloadBuilder, PayloadConfig, }; use reth_node_api::{ - validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, - EngineTypes, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes, + payload::{EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes}, + validate_version_specific_fields, EngineTypes, PayloadAttributes, PayloadBuilderAttributes, }; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::node::{ @@ -53,9 +58,6 @@ use reth_rpc_types::{ ExecutionPayloadV1, Withdrawal, }; use reth_tracing::{RethTracer, Tracer}; -use serde::{Deserialize, Serialize}; -use std::convert::Infallible; -use thiserror::Error; /// A custom payload attributes type. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]