feat(l2-withdrawals): Decompose ExecutionPayloadValidator::ensure_well_formed_payload (#14566)

This commit is contained in:
Emilia Hane
2025-02-19 10:25:29 +01:00
committed by GitHub
parent 974cfa192f
commit 849c04cb34
6 changed files with 214 additions and 85 deletions

3
Cargo.lock generated
View File

@ -8866,7 +8866,8 @@ dependencies = [
name = "reth-payload-validator"
version = "1.2.0"
dependencies = [
"alloy-rpc-types",
"alloy-consensus",
"alloy-rpc-types-engine",
"reth-chainspec",
"reth-primitives",
"reth-primitives-traits",

View File

@ -18,4 +18,5 @@ reth-primitives.workspace = true
reth-primitives-traits.workspace = true
# alloy
alloy-rpc-types = { workspace = true, features = ["engine"] }
alloy-rpc-types-engine.workspace = true
alloy-consensus.workspace = true

View File

@ -0,0 +1,119 @@
//! Cancun rules for new payloads.
use alloy_consensus::{BlockBody, Transaction, Typed2718};
use alloy_rpc_types_engine::{CancunPayloadFields, PayloadError};
use reth_primitives_traits::{AlloyBlockHeader, Block, SealedBlock};
/// Checks block and sidecar w.r.t new Cancun fields and new transaction type EIP-4844.
///
/// Checks that:
/// - Cancun fields are present if Cancun is active
/// - contains EIP-4844 transactions if Cancun is active and vv
/// - checks blob versioned hashes in block and sidecar match
#[inline]
pub fn ensure_well_formed_fields<T, B>(
block: &SealedBlock<B>,
cancun_sidecar_fields: Option<&CancunPayloadFields>,
is_cancun_active: bool,
) -> Result<(), PayloadError>
where
T: Transaction + Typed2718,
B: Block<Body = BlockBody<T>>,
{
ensure_well_formed_header_and_sidecar_fields(block, cancun_sidecar_fields, is_cancun_active)?;
ensure_well_formed_transactions_field_with_sidecar(
block.body(),
cancun_sidecar_fields,
is_cancun_active,
)
}
/// Checks that Cancun fields on block header and sidecar are present if Cancun is active and vv.
#[inline]
pub fn ensure_well_formed_header_and_sidecar_fields<T: Block>(
block: &SealedBlock<T>,
cancun_sidecar_fields: Option<&CancunPayloadFields>,
is_cancun_active: bool,
) -> Result<(), PayloadError> {
if is_cancun_active {
if block.blob_gas_used().is_none() {
// cancun active but blob gas used not present
return Err(PayloadError::PostCancunBlockWithoutBlobGasUsed)
}
if block.excess_blob_gas().is_none() {
// cancun active but excess blob gas not present
return Err(PayloadError::PostCancunBlockWithoutExcessBlobGas)
}
if cancun_sidecar_fields.is_none() {
// cancun active but cancun fields not present
return Err(PayloadError::PostCancunWithoutCancunFields)
}
} else {
if block.blob_gas_used().is_some() {
// cancun not active but blob gas used present
return Err(PayloadError::PreCancunBlockWithBlobGasUsed)
}
if block.excess_blob_gas().is_some() {
// cancun not active but excess blob gas present
return Err(PayloadError::PreCancunBlockWithExcessBlobGas)
}
if cancun_sidecar_fields.is_some() {
// cancun not active but cancun fields present
return Err(PayloadError::PreCancunWithCancunFields)
}
}
Ok(())
}
/// Checks transactions field and sidecar w.r.t new Cancun fields and new transaction type EIP-4844.
///
/// Checks that:
/// - contains EIP-4844 transactions if Cancun is active
/// - checks blob versioned hashes in block and sidecar match
#[inline]
pub fn ensure_well_formed_transactions_field_with_sidecar<T: Transaction + Typed2718>(
block_body: &BlockBody<T>,
cancun_sidecar_fields: Option<&CancunPayloadFields>,
is_cancun_active: bool,
) -> Result<(), PayloadError> {
if is_cancun_active {
ensure_matching_blob_versioned_hashes(block_body, cancun_sidecar_fields)?
} else if block_body.has_eip4844_transactions() {
return Err(PayloadError::PreCancunBlockWithBlobTransactions)
}
Ok(())
}
/// Ensures that the number of blob versioned hashes of a EIP-4844 transactions in block, matches
/// the number hashes included in the _separate_ `block_versioned_hashes` of the cancun payload
/// fields on the sidecar.
pub fn ensure_matching_blob_versioned_hashes<T: Transaction + Typed2718>(
block_body: &BlockBody<T>,
cancun_sidecar_fields: Option<&CancunPayloadFields>,
) -> Result<(), PayloadError> {
let num_blob_versioned_hashes = block_body.blob_versioned_hashes_iter().count();
// Additional Cancun checks for blob transactions
if let Some(versioned_hashes) = cancun_sidecar_fields.map(|fields| &fields.versioned_hashes) {
if num_blob_versioned_hashes != versioned_hashes.len() {
// Number of blob versioned hashes does not match
return Err(PayloadError::InvalidVersionedHashes)
}
// we can use `zip` safely here because we already compared their length
for (payload_versioned_hash, block_versioned_hash) in
versioned_hashes.iter().zip(block_body.blob_versioned_hashes_iter())
{
if payload_versioned_hash != block_versioned_hash {
return Err(PayloadError::InvalidVersionedHashes)
}
}
} else {
// No Cancun fields, if block includes any blobs, this is an error
if num_blob_versioned_hashes > 0 {
return Err(PayloadError::InvalidVersionedHashes)
}
}
Ok(())
}

View File

@ -8,7 +8,11 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
use alloy_rpc_types::engine::{ExecutionData, MaybeCancunPayloadFields, PayloadError};
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};
@ -53,40 +57,6 @@ impl<ChainSpec: EthereumHardforks> ExecutionPayloadValidator<ChainSpec> {
self.chain_spec().is_prague_active_at_timestamp(timestamp)
}
/// Cancun specific checks for EIP-4844 blob transactions.
///
/// Ensures that the number of blob versioned hashes matches the number hashes included in the
/// _separate_ `block_versioned_hashes` of the cancun payload fields.
fn ensure_matching_blob_versioned_hashes<B: Block>(
&self,
sealed_block: &SealedBlock<B>,
cancun_fields: &MaybeCancunPayloadFields,
) -> Result<(), PayloadError> {
let num_blob_versioned_hashes = sealed_block.blob_versioned_hashes_iter().count();
// Additional Cancun checks for blob transactions
if let Some(versioned_hashes) = cancun_fields.versioned_hashes() {
if num_blob_versioned_hashes != versioned_hashes.len() {
// Number of blob versioned hashes does not match
return Err(PayloadError::InvalidVersionedHashes)
}
// we can use `zip` safely here because we already compared their length
for (payload_versioned_hash, block_versioned_hash) in
versioned_hashes.iter().zip(sealed_block.blob_versioned_hashes_iter())
{
if payload_versioned_hash != block_versioned_hash {
return Err(PayloadError::InvalidVersionedHashes)
}
}
} else {
// No Cancun fields, if block includes any blobs, this is an error
if num_blob_versioned_hashes > 0 {
return Err(PayloadError::InvalidVersionedHashes)
}
}
Ok(())
}
/// Ensures that the given payload does not violate any consensus rules that concern the block's
/// layout, like:
/// - missing or invalid base fee
@ -100,11 +70,11 @@ impl<ChainSpec: EthereumHardforks> ExecutionPayloadValidator<ChainSpec> {
/// 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`] 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>
/// 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
/// [`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`].
///
@ -129,54 +99,21 @@ impl<ChainSpec: EthereumHardforks> ExecutionPayloadValidator<ChainSpec> {
})
}
if self.is_cancun_active_at_timestamp(sealed_block.timestamp) {
if sealed_block.blob_gas_used.is_none() {
// cancun active but blob gas used not present
return Err(PayloadError::PostCancunBlockWithoutBlobGasUsed)
}
if sealed_block.excess_blob_gas.is_none() {
// cancun active but excess blob gas not present
return Err(PayloadError::PostCancunBlockWithoutExcessBlobGas)
}
if sidecar.cancun().is_none() {
// cancun active but cancun fields not present
return Err(PayloadError::PostCancunWithoutCancunFields)
}
} else {
if sealed_block.body().has_eip4844_transactions() {
// cancun not active but blob transactions present
return Err(PayloadError::PreCancunBlockWithBlobTransactions)
}
if sealed_block.blob_gas_used.is_some() {
// cancun not active but blob gas used present
return Err(PayloadError::PreCancunBlockWithBlobGasUsed)
}
if sealed_block.excess_blob_gas.is_some() {
// cancun not active but excess blob gas present
return Err(PayloadError::PreCancunBlockWithExcessBlobGas)
}
if sidecar.cancun().is_some() {
// cancun not active but cancun fields present
return Err(PayloadError::PreCancunWithCancunFields)
}
}
shanghai::ensure_well_formed_fields(
sealed_block.body(),
self.is_shanghai_active_at_timestamp(sealed_block.timestamp),
)?;
let shanghai_active = self.is_shanghai_active_at_timestamp(sealed_block.timestamp);
if !shanghai_active && sealed_block.body().withdrawals.is_some() {
// shanghai not active but withdrawals present
return Err(PayloadError::PreShanghaiBlockWithWithdrawals)
}
if !self.is_prague_active_at_timestamp(sealed_block.timestamp) &&
sealed_block.body().has_eip7702_transactions()
{
return Err(PayloadError::PrePragueBlockWithEip7702Transactions)
}
// EIP-4844 checks
self.ensure_matching_blob_versioned_hashes(
cancun::ensure_well_formed_fields(
&sealed_block,
&sidecar.cancun().cloned().into(),
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

@ -0,0 +1,48 @@
//! Prague rules for new payloads.
use alloy_consensus::{BlockBody, Typed2718};
use alloy_rpc_types_engine::{PayloadError, PraguePayloadFields};
/// Checks block and sidecar well-formedness w.r.t new Prague fields and new transaction type
/// EIP-7702.
///
/// Checks that:
/// - Prague fields are not present unless Prague is active
/// - does not contain EIP-7702 transactions if Prague is not active
#[inline]
pub fn ensure_well_formed_fields<T: Typed2718>(
block_body: &BlockBody<T>,
prague_fields: Option<&PraguePayloadFields>,
is_prague_active: bool,
) -> Result<(), PayloadError> {
ensure_well_formed_sidecar_fields(prague_fields, is_prague_active)?;
ensure_well_formed_transactions_field(block_body, is_prague_active)
}
/// Checks that Prague fields are not present on sidecar unless Prague is active.
#[inline]
pub fn ensure_well_formed_sidecar_fields(
prague_fields: Option<&PraguePayloadFields>,
is_prague_active: bool,
) -> Result<(), PayloadError> {
if !is_prague_active && prague_fields.is_some() {
// prague _not_ active but prague fields present
return Err(PayloadError::PrePragueBlockRequests)
}
Ok(())
}
/// Checks that transactions field doesn't contain EIP-7702 transactions if Prague is not
/// active.
#[inline]
pub fn ensure_well_formed_transactions_field<T: Typed2718>(
block_body: &BlockBody<T>,
is_prague_active: bool,
) -> Result<(), PayloadError> {
if !is_prague_active && block_body.has_eip7702_transactions() {
return Err(PayloadError::PrePragueBlockWithEip7702Transactions)
}
Ok(())
}

View File

@ -0,0 +1,23 @@
//! Shanghai rules for new payloads.
use alloy_rpc_types_engine::PayloadError;
use reth_primitives_traits::BlockBody;
/// Checks that block body contains withdrawals if Shanghai is active and vv.
#[inline]
pub fn ensure_well_formed_fields<T: BlockBody>(
block_body: &T,
is_shanghai_active: bool,
) -> Result<(), PayloadError> {
if is_shanghai_active {
if block_body.withdrawals().is_none() {
// shanghai not active but withdrawals present
return Err(PayloadError::PostShanghaiBlockWithoutWithdrawals)
}
} else if block_body.withdrawals().is_some() {
// shanghai not active but withdrawals present
return Err(PayloadError::PreShanghaiBlockWithWithdrawals)
}
Ok(())
}