chore(payload): Move ExecutionPayloadValidator into reth-ethereum-payload-builder (#14751)

This commit is contained in:
Emilia Hane
2025-02-28 14:05:14 +01:00
committed by GitHub
parent 732e3debe2
commit 6ce459aec7
24 changed files with 241 additions and 202 deletions

View File

@@ -41,6 +41,7 @@ reth-evm-ethereum.workspace = true
reth-exex-types.workspace = true
reth-primitives.workspace = true
reth-chainspec.workspace = true
reth-node-ethereum.workspace = true
tokio = { workspace = true, features = ["sync"] }
tokio-stream.workspace = true

View File

@@ -163,10 +163,11 @@ mod tests {
use reth_engine_primitives::BeaconEngineMessage;
use reth_engine_tree::{test_utils::TestPipelineBuilder, tree::NoopInvalidBlockHook};
use reth_ethereum_consensus::EthBeaconConsensus;
use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator};
use reth_ethereum_engine_primitives::EthEngineTypes;
use reth_evm_ethereum::{execute::EthExecutorProvider, EthEvmConfig};
use reth_exex_types::FinishedExExHeight;
use reth_network_p2p::test_utils::TestFullBlockClient;
use reth_node_ethereum::EthereumEngineValidator;
use reth_primitives::SealedHeader;
use reth_provider::{
providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec,

View File

@@ -85,6 +85,7 @@ reth-static-file.workspace = true
reth-testing-utils.workspace = true
reth-tracing.workspace = true
reth-trie-db.workspace = true
reth-node-ethereum.workspace = true
# alloy
alloy-rlp.workspace = true
@@ -128,4 +129,5 @@ test-utils = [
"reth-trie-db/test-utils",
"reth-trie-parallel/test-utils",
"reth-ethereum-primitives/test-utils",
"reth-node-ethereum/test-utils",
]

View File

@@ -2897,10 +2897,11 @@ mod tests {
use reth_chainspec::{ChainSpec, HOLESKY, MAINNET};
use reth_engine_primitives::ForkchoiceStatus;
use reth_ethereum_consensus::EthBeaconConsensus;
use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator};
use reth_ethereum_engine_primitives::EthEngineTypes;
use reth_ethereum_primitives::{Block, EthPrimitives};
use reth_evm::test_utils::MockExecutorProvider;
use reth_evm_ethereum::EthEvmConfig;
use reth_node_ethereum::EthereumEngineValidator;
use reth_primitives_traits::Block as _;
use reth_provider::test_utils::MockEthProvider;
use reth_trie::{updates::TrieUpdates, HashedPostState};

View File

@@ -20,7 +20,6 @@ reth-chainspec.workspace = true
reth-consensus-common.workspace = true
reth-fs-util.workspace = true
reth-engine-primitives.workspace = true
reth-payload-validator.workspace = true
reth-evm.workspace = true
reth-revm.workspace = true
reth-provider.workspace = true

View File

@@ -12,11 +12,9 @@ workspace = true
[dependencies]
# reth
reth-chainspec.workspace = true
reth-primitives.workspace = true
reth-engine-primitives.workspace = true
reth-payload-primitives.workspace = true
reth-payload-validator.workspace = true
# alloy
alloy-primitives.workspace = true
@@ -34,7 +32,6 @@ serde_json.workspace = true
[features]
default = ["std"]
std = [
"reth-chainspec/std",
"reth-primitives/std",
"alloy-primitives/std",
"alloy-eips/std",

View File

@@ -12,21 +12,16 @@
extern crate alloc;
mod payload;
use alloc::sync::Arc;
pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes};
use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload};
pub use alloy_rpc_types_engine::{
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes,
};
pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes};
use reth_chainspec::ChainSpec;
use reth_engine_primitives::{EngineTypes, EngineValidator, PayloadValidator};
use reth_payload_primitives::{
validate_version_specific_fields, BuiltPayload, EngineApiMessageVersion,
EngineObjectValidationError, NewPayloadError, PayloadOrAttributes, PayloadTypes,
};
use reth_payload_validator::ExecutionPayloadValidator;
use reth_primitives::{Block, NodePrimitives, RecoveredBlock, SealedBlock};
use reth_engine_primitives::EngineTypes;
use reth_payload_primitives::{BuiltPayload, PayloadTypes};
use reth_primitives::{NodePrimitives, SealedBlock};
/// The types used in the default mainnet ethereum beacon consensus engine.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
@@ -77,62 +72,3 @@ impl PayloadTypes for EthPayloadTypes {
type PayloadAttributes = EthPayloadAttributes;
type PayloadBuilderAttributes = EthPayloadBuilderAttributes;
}
/// Validator for the ethereum engine API.
#[derive(Debug, Clone)]
pub struct EthereumEngineValidator {
inner: ExecutionPayloadValidator<ChainSpec>,
}
impl EthereumEngineValidator {
/// Instantiates a new validator.
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
Self { inner: ExecutionPayloadValidator::new(chain_spec) }
}
/// Returns the chain spec used by the validator.
#[inline]
fn chain_spec(&self) -> &ChainSpec {
self.inner.chain_spec()
}
}
impl PayloadValidator for EthereumEngineValidator {
type Block = Block;
type ExecutionData = ExecutionData;
fn ensure_well_formed_payload(
&self,
payload: ExecutionData,
) -> Result<RecoveredBlock<Self::Block>, NewPayloadError> {
let sealed_block = self.inner.ensure_well_formed_payload(payload)?;
sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into()))
}
}
impl<Types> EngineValidator<Types> for EthereumEngineValidator
where
Types: EngineTypes<PayloadAttributes = EthPayloadAttributes, ExecutionData = ExecutionData>,
{
fn validate_version_specific_fields(
&self,
version: EngineApiMessageVersion,
payload_or_attrs: PayloadOrAttributes<'_, Self::ExecutionData, EthPayloadAttributes>,
) -> Result<(), EngineObjectValidationError> {
validate_version_specific_fields(self.chain_spec(), version, payload_or_attrs)
}
fn ensure_well_formed_attributes(
&self,
version: EngineApiMessageVersion,
attributes: &EthPayloadAttributes,
) -> Result<(), EngineObjectValidationError> {
validate_version_specific_fields(
self.chain_spec(),
version,
PayloadOrAttributes::<Self::ExecutionData, EthPayloadAttributes>::PayloadAttributes(
attributes,
),
)
}
}

View File

@@ -33,9 +33,13 @@ reth-chainspec.workspace = true
reth-revm = { workspace = true, features = ["std"] }
reth-trie-db.workspace = true
reth-rpc-eth-types.workspace = true
reth-engine-primitives.workspace = true
reth-primitives.workspace = true
reth-payload-primitives.workspace = true
# ethereum
alloy-rpc-types-eth.workspace = true
alloy-rpc-types-engine.workspace = true
# revm with required ethereum features
revm = { workspace = true, features = ["secp256k1", "blst", "c-kzg"] }
@@ -87,4 +91,5 @@ test-utils = [
"reth-trie-db/test-utils",
"revm/test-utils",
"reth-evm/test-utils",
"reth-primitives/test-utils",
]

View File

@@ -0,0 +1,75 @@
//! Validates execution payload wrt Ethereum Execution Engine API version.
use alloy_rpc_types_engine::ExecutionData;
pub use alloy_rpc_types_engine::{
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes,
};
use reth_chainspec::ChainSpec;
use reth_engine_primitives::{EngineTypes, EngineValidator, PayloadValidator};
use reth_ethereum_payload_builder::EthereumExecutionPayloadValidator;
use reth_payload_primitives::{
validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError,
NewPayloadError, PayloadOrAttributes,
};
use reth_primitives::{Block, RecoveredBlock};
use std::sync::Arc;
/// Validator for the ethereum engine API.
#[derive(Debug, Clone)]
pub struct EthereumEngineValidator {
inner: EthereumExecutionPayloadValidator<ChainSpec>,
}
impl EthereumEngineValidator {
/// Instantiates a new validator.
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
Self { inner: EthereumExecutionPayloadValidator::new(chain_spec) }
}
/// Returns the chain spec used by the validator.
#[inline]
fn chain_spec(&self) -> &ChainSpec {
self.inner.chain_spec()
}
}
impl PayloadValidator for EthereumEngineValidator {
type Block = Block;
type ExecutionData = ExecutionData;
fn ensure_well_formed_payload(
&self,
payload: ExecutionData,
) -> Result<RecoveredBlock<Self::Block>, NewPayloadError> {
let sealed_block = self.inner.ensure_well_formed_payload(payload)?;
sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into()))
}
}
impl<Types> EngineValidator<Types> for EthereumEngineValidator
where
Types: EngineTypes<PayloadAttributes = EthPayloadAttributes, ExecutionData = ExecutionData>,
{
fn validate_version_specific_fields(
&self,
version: EngineApiMessageVersion,
payload_or_attrs: PayloadOrAttributes<'_, Self::ExecutionData, EthPayloadAttributes>,
) -> Result<(), EngineObjectValidationError> {
validate_version_specific_fields(self.chain_spec(), version, payload_or_attrs)
}
fn ensure_well_formed_attributes(
&self,
version: EngineApiMessageVersion,
attributes: &EthPayloadAttributes,
) -> Result<(), EngineObjectValidationError> {
validate_version_specific_fields(
self.chain_spec(),
version,
PayloadOrAttributes::<Self::ExecutionData, EthPayloadAttributes>::PayloadAttributes(
attributes,
),
)
}
}

View File

@@ -24,3 +24,6 @@ pub mod node;
pub use node::EthereumNode;
pub mod payload;
pub mod engine;
pub use engine::EthereumEngineValidator;

View File

@@ -1,11 +1,10 @@
//! Ethereum Node types config.
pub use crate::payload::EthereumPayloadBuilder;
pub use crate::{payload::EthereumPayloadBuilder, EthereumEngineValidator};
use crate::{EthEngineTypes, EthEvmConfig};
use reth_chainspec::ChainSpec;
use reth_consensus::{ConsensusError, FullConsensus};
use reth_ethereum_consensus::EthBeaconConsensus;
pub use reth_ethereum_engine_primitives::EthereumEngineValidator;
use reth_ethereum_engine_primitives::{
EthBuiltPayload, EthPayloadAttributes, EthPayloadBuilderAttributes,
};

View File

@@ -27,9 +27,12 @@ reth-evm.workspace = true
reth-evm-ethereum.workspace = true
reth-errors.workspace = true
reth-chainspec.workspace = true
reth-payload-validator.workspace = true
reth-primitives.workspace = true
# ethereum
revm.workspace = true
alloy-rpc-types-engine.workspace = true
# alloy
alloy-eips.workspace = true

View File

@@ -9,6 +9,9 @@
#![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::{BlockHeader, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH};
use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, merge::BEACON_NONCE};
use alloy_primitives::U256;

View File

@@ -0,0 +1,110 @@
//! Validates execution payload wrt Ethereum consensus rules
use alloy_rpc_types_engine::{ExecutionData, PayloadError};
use reth_chainspec::EthereumHardforks;
use reth_payload_validator::{cancun, prague, shanghai};
use reth_primitives::Block;
use reth_primitives_traits::{Block as _, SealedBlock, SignedTransaction};
use std::sync::Arc;
/// Execution payload validator.
#[derive(Clone, Debug)]
pub struct EthereumExecutionPayloadValidator<ChainSpec> {
/// Chain spec to validate against.
chain_spec: Arc<ChainSpec>,
}
impl<ChainSpec> EthereumExecutionPayloadValidator<ChainSpec> {
/// Create a new validator.
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
Self { chain_spec }
}
/// Returns the chain spec used by the validator.
#[inline]
pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
&self.chain_spec
}
}
impl<ChainSpec: EthereumHardforks> EthereumExecutionPayloadValidator<ChainSpec> {
/// Returns true if the Cancun hardfork is active at the given timestamp.
#[inline]
fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool {
self.chain_spec().is_cancun_active_at_timestamp(timestamp)
}
/// Returns true if the Shanghai hardfork is active at the given timestamp.
#[inline]
fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool {
self.chain_spec().is_shanghai_active_at_timestamp(timestamp)
}
/// Returns true if the Prague hardfork is active at the given timestamp.
#[inline]
fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool {
self.chain_spec().is_prague_active_at_timestamp(timestamp)
}
/// 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
/// - the versioned hashes passed with the payload do not exactly match transaction versioned
/// hashes
/// - the block does not contain blob transactions if it is pre-cancun
///
/// 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 [`MaybeCancunPayloadFields`](alloy_rpc_types_engine::MaybeCancunPayloadFields) are not part of the payload, but are additional fields in the `engine_newPayloadV3` RPC call, See also <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#engine_newpayloadv3>
///
/// If the cancun fields are provided this also validates that the versioned hashes in the block
/// match the versioned hashes passed in the
/// [`CancunPayloadFields`](alloy_rpc_types_engine::CancunPayloadFields), if the cancun payload
/// fields are provided. If the payload fields are not provided, but versioned hashes exist
/// in the block, this is considered an error: [`PayloadError::InvalidVersionedHashes`].
///
/// This validates versioned hashes according to the Engine API Cancun spec:
/// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification>
pub fn ensure_well_formed_payload<T: SignedTransaction>(
&self,
payload: ExecutionData,
) -> Result<SealedBlock<Block<T>>, PayloadError> {
let ExecutionData { 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_fields(
&sealed_block,
sidecar.cancun(),
self.is_cancun_active_at_timestamp(sealed_block.timestamp),
)?;
prague::ensure_well_formed_fields(
sealed_block.body(),
sidecar.prague(),
self.is_prague_active_at_timestamp(sealed_block.timestamp),
)?;
Ok(sealed_block)
}
}

View File

@@ -13,10 +13,16 @@ workspace = true
[dependencies]
# reth
reth-chainspec.workspace = true
reth-primitives.workspace = true
reth-primitives-traits.workspace = true
# alloy
alloy-rpc-types-engine.workspace = true
alloy-consensus.workspace = true
[features]
default = ["std"]
std = [
"alloy-consensus/std",
"alloy-rpc-types-engine/std",
"reth-primitives-traits/std",
]

View File

@@ -7,115 +7,8 @@
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
pub mod cancun;
pub mod prague;
pub mod shanghai;
use alloy_rpc_types_engine::{ExecutionData, PayloadError};
use reth_chainspec::EthereumHardforks;
use reth_primitives::SealedBlock;
use reth_primitives_traits::{Block, SignedTransaction};
use std::sync::Arc;
/// Execution payload validator.
#[derive(Clone, Debug)]
pub struct ExecutionPayloadValidator<ChainSpec> {
/// Chain spec to validate against.
chain_spec: Arc<ChainSpec>,
}
impl<ChainSpec> ExecutionPayloadValidator<ChainSpec> {
/// Create a new validator.
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
Self { chain_spec }
}
/// Returns the chain spec used by the validator.
#[inline]
pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
&self.chain_spec
}
}
impl<ChainSpec: EthereumHardforks> ExecutionPayloadValidator<ChainSpec> {
/// Returns true if the Cancun hardfork is active at the given timestamp.
#[inline]
fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool {
self.chain_spec().is_cancun_active_at_timestamp(timestamp)
}
/// Returns true if the Shanghai hardfork is active at the given timestamp.
#[inline]
fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool {
self.chain_spec().is_shanghai_active_at_timestamp(timestamp)
}
/// Returns true if the Prague hardfork is active at the given timestamp.
#[inline]
fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool {
self.chain_spec().is_prague_active_at_timestamp(timestamp)
}
/// 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
/// - the versioned hashes passed with the payload do not exactly match transaction versioned
/// hashes
/// - the block does not contain blob transactions if it is pre-cancun
///
/// 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 [`MaybeCancunPayloadFields`](alloy_rpc_types_engine::MaybeCancunPayloadFields) are not part of the payload, but are additional fields in the `engine_newPayloadV3` RPC call, See also <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#engine_newpayloadv3>
///
/// If the cancun fields are provided this also validates that the versioned hashes in the block
/// match the versioned hashes passed in the
/// [`CancunPayloadFields`](alloy_rpc_types_engine::CancunPayloadFields), if the cancun payload
/// fields are provided. If the payload fields are not provided, but versioned hashes exist
/// in the block, this is considered an error: [`PayloadError::InvalidVersionedHashes`].
///
/// This validates versioned hashes according to the Engine API Cancun spec:
/// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification>
pub fn ensure_well_formed_payload<T: SignedTransaction>(
&self,
payload: ExecutionData,
) -> Result<SealedBlock<reth_primitives::Block<T>>, PayloadError> {
let ExecutionData { 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_fields(
&sealed_block,
sidecar.cancun(),
self.is_cancun_active_at_timestamp(sealed_block.timestamp),
)?;
prague::ensure_well_formed_fields(
sealed_block.body(),
sidecar.prague(),
self.is_prague_active_at_timestamp(sealed_block.timestamp),
)?;
Ok(sealed_block)
}
}

View File

@@ -66,6 +66,7 @@ reth-transaction-pool = { workspace = true, features = ["test-utils"] }
reth-rpc-types-compat.workspace = true
reth-primitives.workspace = true
reth-engine-primitives.workspace = true
reth-node-ethereum.workspace = true
alloy-primitives.workspace = true
alloy-rpc-types-eth.workspace = true

View File

@@ -4,10 +4,11 @@ use alloy_rpc_types_engine::{ClientCode, ClientVersionV1};
use reth_chainspec::MAINNET;
use reth_consensus::noop::NoopConsensus;
use reth_engine_primitives::BeaconConsensusEngineHandle;
use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator};
use reth_ethereum_engine_primitives::EthEngineTypes;
use reth_evm::execute::BasicBlockExecutorProvider;
use reth_evm_ethereum::EthEvmConfig;
use reth_network_api::noop::NoopNetwork;
use reth_node_ethereum::EthereumEngineValidator;
use reth_payload_builder::test_utils::spawn_test_payload_service;
use reth_provider::test_utils::NoopProvider;
use reth_rpc::EthApi;

View File

@@ -53,5 +53,6 @@ reth-primitives-traits.workspace = true
reth-payload-builder = { workspace = true, features = ["test-utils"] }
reth-testing-utils.workspace = true
alloy-rlp.workspace = true
reth-node-ethereum.workspace = true
assert_matches.workspace = true

View File

@@ -1160,8 +1160,9 @@ mod tests {
use assert_matches::assert_matches;
use reth_chainspec::{ChainSpec, EthereumHardfork, MAINNET};
use reth_engine_primitives::BeaconEngineMessage;
use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator};
use reth_ethereum_engine_primitives::EthEngineTypes;
use reth_ethereum_primitives::Block;
use reth_node_ethereum::EthereumEngineValidator;
use reth_payload_builder::test_utils::spawn_test_payload_service;
use reth_provider::test_utils::MockEthProvider;
use reth_tasks::TokioTaskExecutor;