mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(l2-withdrawals): Decompose ExecutionPayloadValidator::ensure_well_formed_payload (#14566)
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
119
crates/payload/validator/src/cancun.rs
Normal file
119
crates/payload/validator/src/cancun.rs
Normal 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(())
|
||||
}
|
||||
@ -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)
|
||||
|
||||
48
crates/payload/validator/src/prague.rs
Normal file
48
crates/payload/validator/src/prague.rs
Normal 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(())
|
||||
}
|
||||
23
crates/payload/validator/src/shanghai.rs
Normal file
23
crates/payload/validator/src/shanghai.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user