mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
chore: move header validation from reth-primitives to consensus crates (#8802)
This commit is contained in:
@ -9,56 +9,29 @@ use reth_primitives::{
|
|||||||
ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader,
|
ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Validate header standalone
|
/// Gas used needs to be less than gas limit. Gas used is going to be checked after execution.
|
||||||
pub fn validate_header_standalone(
|
#[inline]
|
||||||
header: &SealedHeader,
|
pub fn validate_header_gas(header: &SealedHeader) -> Result<(), ConsensusError> {
|
||||||
chain_spec: &ChainSpec,
|
|
||||||
) -> Result<(), ConsensusError> {
|
|
||||||
// Gas used needs to be less than gas limit. Gas used is going to be checked after execution.
|
|
||||||
if header.gas_used > header.gas_limit {
|
if header.gas_used > header.gas_limit {
|
||||||
return Err(ConsensusError::HeaderGasUsedExceedsGasLimit {
|
return Err(ConsensusError::HeaderGasUsedExceedsGasLimit {
|
||||||
gas_used: header.gas_used,
|
gas_used: header.gas_used,
|
||||||
gas_limit: header.gas_limit,
|
gas_limit: header.gas_limit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Check if base fee is set.
|
/// Ensure the EIP-1559 base fee is set if the London hardfork is active.
|
||||||
|
#[inline]
|
||||||
|
pub fn validate_header_base_fee(
|
||||||
|
header: &SealedHeader,
|
||||||
|
chain_spec: &ChainSpec,
|
||||||
|
) -> Result<(), ConsensusError> {
|
||||||
if chain_spec.fork(Hardfork::London).active_at_block(header.number) &&
|
if chain_spec.fork(Hardfork::London).active_at_block(header.number) &&
|
||||||
header.base_fee_per_gas.is_none()
|
header.base_fee_per_gas.is_none()
|
||||||
{
|
{
|
||||||
return Err(ConsensusError::BaseFeeMissing)
|
return Err(ConsensusError::BaseFeeMissing)
|
||||||
}
|
}
|
||||||
|
|
||||||
let wd_root_missing = header.withdrawals_root.is_none() && !chain_spec.is_optimism();
|
|
||||||
|
|
||||||
// EIP-4895: Beacon chain push withdrawals as operations
|
|
||||||
if chain_spec.is_shanghai_active_at_timestamp(header.timestamp) && wd_root_missing {
|
|
||||||
return Err(ConsensusError::WithdrawalsRootMissing)
|
|
||||||
} else if !chain_spec.is_shanghai_active_at_timestamp(header.timestamp) &&
|
|
||||||
header.withdrawals_root.is_some()
|
|
||||||
{
|
|
||||||
return Err(ConsensusError::WithdrawalsRootUnexpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensures that EIP-4844 fields are valid once cancun is active.
|
|
||||||
if chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
|
|
||||||
validate_4844_header_standalone(header)?;
|
|
||||||
} else if header.blob_gas_used.is_some() {
|
|
||||||
return Err(ConsensusError::BlobGasUsedUnexpected)
|
|
||||||
} else if header.excess_blob_gas.is_some() {
|
|
||||||
return Err(ConsensusError::ExcessBlobGasUnexpected)
|
|
||||||
} else if header.parent_beacon_block_root.is_some() {
|
|
||||||
return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
if chain_spec.is_prague_active_at_timestamp(header.timestamp) {
|
|
||||||
if header.requests_root.is_none() {
|
|
||||||
return Err(ConsensusError::RequestsRootMissing)
|
|
||||||
}
|
|
||||||
} else if header.requests_root.is_some() {
|
|
||||||
return Err(ConsensusError::RequestsRootUnexpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,6 +148,7 @@ pub fn validate_4844_header_standalone(header: &SealedHeader) -> Result<(), Cons
|
|||||||
///
|
///
|
||||||
/// From yellow paper: extraData: An arbitrary byte array containing data relevant to this block.
|
/// From yellow paper: extraData: An arbitrary byte array containing data relevant to this block.
|
||||||
/// This must be 32 bytes or fewer; formally Hx.
|
/// This must be 32 bytes or fewer; formally Hx.
|
||||||
|
#[inline]
|
||||||
pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError> {
|
pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError> {
|
||||||
if header.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
|
if header.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
|
||||||
Err(ConsensusError::ExtraDataExceedsMax { len: header.extra_data.len() })
|
Err(ConsensusError::ExtraDataExceedsMax { len: header.extra_data.len() })
|
||||||
@ -183,6 +157,78 @@ pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates against the parent hash and number.
|
||||||
|
///
|
||||||
|
/// This function ensures that the header block number is sequential and that the hash of the parent
|
||||||
|
/// header matches the parent hash in the header.
|
||||||
|
#[inline]
|
||||||
|
pub fn validate_against_parent_hash_number(
|
||||||
|
header: &SealedHeader,
|
||||||
|
parent: &SealedHeader,
|
||||||
|
) -> Result<(), ConsensusError> {
|
||||||
|
// Parent number is consistent.
|
||||||
|
if parent.number + 1 != header.number {
|
||||||
|
return Err(ConsensusError::ParentBlockNumberMismatch {
|
||||||
|
parent_block_number: parent.number,
|
||||||
|
block_number: header.number,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.hash() != header.parent_hash {
|
||||||
|
return Err(ConsensusError::ParentHashMismatch(
|
||||||
|
GotExpected { got: header.parent_hash, expected: parent.hash() }.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the base fee against the parent and EIP-1559 rules.
|
||||||
|
#[inline]
|
||||||
|
pub fn validate_against_parent_eip1559_base_fee(
|
||||||
|
header: &SealedHeader,
|
||||||
|
parent: &SealedHeader,
|
||||||
|
chain_spec: &ChainSpec,
|
||||||
|
) -> Result<(), ConsensusError> {
|
||||||
|
if chain_spec.fork(Hardfork::London).active_at_block(header.number) {
|
||||||
|
let base_fee = header.base_fee_per_gas.ok_or(ConsensusError::BaseFeeMissing)?;
|
||||||
|
|
||||||
|
let expected_base_fee =
|
||||||
|
if chain_spec.fork(Hardfork::London).transitions_at_block(header.number) {
|
||||||
|
reth_primitives::constants::EIP1559_INITIAL_BASE_FEE
|
||||||
|
} else {
|
||||||
|
// This BaseFeeMissing will not happen as previous blocks are checked to have
|
||||||
|
// them.
|
||||||
|
parent
|
||||||
|
.next_block_base_fee(chain_spec.base_fee_params_at_timestamp(header.timestamp))
|
||||||
|
.ok_or(ConsensusError::BaseFeeMissing)?
|
||||||
|
};
|
||||||
|
if expected_base_fee != base_fee {
|
||||||
|
return Err(ConsensusError::BaseFeeDiff(GotExpected {
|
||||||
|
expected: expected_base_fee,
|
||||||
|
got: base_fee,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the timestamp against the parent to make sure it is in the past.
|
||||||
|
#[inline]
|
||||||
|
pub fn validate_against_parent_timestamp(
|
||||||
|
header: &SealedHeader,
|
||||||
|
parent: &SealedHeader,
|
||||||
|
) -> Result<(), ConsensusError> {
|
||||||
|
if header.is_timestamp_in_past(parent.timestamp) {
|
||||||
|
return Err(ConsensusError::TimestampIsInPast {
|
||||||
|
parent_timestamp: parent.timestamp,
|
||||||
|
timestamp: header.timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -410,22 +456,6 @@ mod tests {
|
|||||||
.return_const(Ok(Some(Withdrawal { index: 2, ..Default::default() })));
|
.return_const(Ok(Some(Withdrawal { index: 2, ..Default::default() })));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn shanghai_block_zero_withdrawals() {
|
|
||||||
// ensures that if shanghai is activated, and we include a block with a withdrawals root,
|
|
||||||
// that the header is valid
|
|
||||||
let chain_spec = ChainSpecBuilder::mainnet().shanghai_activated().build();
|
|
||||||
|
|
||||||
let header = Header {
|
|
||||||
base_fee_per_gas: Some(1337u64),
|
|
||||||
withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.seal_slow();
|
|
||||||
|
|
||||||
assert_eq!(validate_header_standalone(&header, &chain_spec), Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cancun_block_incorrect_blob_gas_used() {
|
fn cancun_block_incorrect_blob_gas_used() {
|
||||||
let chain_spec = ChainSpecBuilder::mainnet().cancun_activated().build();
|
let chain_spec = ChainSpecBuilder::mainnet().cancun_activated().build();
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected, GotExpectedBoxed, Header,
|
constants::MINIMUM_GAS_LIMIT, BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected,
|
||||||
HeaderValidationError, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader,
|
GotExpectedBoxed, Header, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader,
|
||||||
B256, U256,
|
B256, U256,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,6 +206,10 @@ pub enum ConsensusError {
|
|||||||
block_number: BlockNumber,
|
block_number: BlockNumber,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Error when the parent hash does not match the expected parent hash.
|
||||||
|
#[error("mismatched parent hash: {0}")]
|
||||||
|
ParentHashMismatch(GotExpectedBoxed<B256>),
|
||||||
|
|
||||||
/// Error when the block timestamp is in the future compared to our clock time.
|
/// Error when the block timestamp is in the future compared to our clock time.
|
||||||
#[error("block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}")]
|
#[error("block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}")]
|
||||||
TimestampIsInFuture {
|
TimestampIsInFuture {
|
||||||
@ -329,9 +333,60 @@ pub enum ConsensusError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidTransaction(#[from] InvalidTransactionError),
|
InvalidTransaction(#[from] InvalidTransactionError),
|
||||||
|
|
||||||
/// Error type transparently wrapping `HeaderValidationError`.
|
/// Error when the block's base fee is different from the expected base fee.
|
||||||
#[error(transparent)]
|
#[error("block base fee mismatch: {0}")]
|
||||||
HeaderValidationError(#[from] HeaderValidationError),
|
BaseFeeDiff(GotExpected<u64>),
|
||||||
|
|
||||||
|
/// Error when there is an invalid excess blob gas.
|
||||||
|
#[error(
|
||||||
|
"invalid excess blob gas: {diff}; \
|
||||||
|
parent excess blob gas: {parent_excess_blob_gas}, \
|
||||||
|
parent blob gas used: {parent_blob_gas_used}"
|
||||||
|
)]
|
||||||
|
ExcessBlobGasDiff {
|
||||||
|
/// The excess blob gas diff.
|
||||||
|
diff: GotExpected<u64>,
|
||||||
|
/// The parent excess blob gas.
|
||||||
|
parent_excess_blob_gas: u64,
|
||||||
|
/// The parent blob gas used.
|
||||||
|
parent_blob_gas_used: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Error when the child gas limit exceeds the maximum allowed increase.
|
||||||
|
#[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")]
|
||||||
|
GasLimitInvalidIncrease {
|
||||||
|
/// The parent gas limit.
|
||||||
|
parent_gas_limit: u64,
|
||||||
|
/// The child gas limit.
|
||||||
|
child_gas_limit: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Error indicating that the child gas limit is below the minimum allowed limit.
|
||||||
|
///
|
||||||
|
/// This error occurs when the child gas limit is less than the specified minimum gas limit.
|
||||||
|
#[error("child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})")]
|
||||||
|
GasLimitInvalidMinimum {
|
||||||
|
/// The child gas limit.
|
||||||
|
child_gas_limit: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Error when the child gas limit exceeds the maximum allowed decrease.
|
||||||
|
#[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")]
|
||||||
|
GasLimitInvalidDecrease {
|
||||||
|
/// The parent gas limit.
|
||||||
|
parent_gas_limit: u64,
|
||||||
|
/// The child gas limit.
|
||||||
|
child_gas_limit: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Error when the block timestamp is in the past compared to the parent timestamp.
|
||||||
|
#[error("block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}")]
|
||||||
|
TimestampIsInPast {
|
||||||
|
/// The parent block's timestamp.
|
||||||
|
parent_timestamp: u64,
|
||||||
|
/// The block's timestamp.
|
||||||
|
timestamp: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConsensusError {
|
impl ConsensusError {
|
||||||
|
|||||||
@ -10,11 +10,15 @@
|
|||||||
|
|
||||||
use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
|
use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
|
||||||
use reth_consensus_common::validation::{
|
use reth_consensus_common::validation::{
|
||||||
validate_block_pre_execution, validate_header_extradata, validate_header_standalone,
|
validate_4844_header_standalone, validate_against_parent_eip1559_base_fee,
|
||||||
|
validate_against_parent_hash_number, validate_against_parent_timestamp,
|
||||||
|
validate_block_pre_execution, validate_header_base_fee, validate_header_extradata,
|
||||||
|
validate_header_gas,
|
||||||
};
|
};
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
BlockWithSenders, Chain, ChainSpec, Hardfork, Header, SealedBlock, SealedHeader,
|
constants::MINIMUM_GAS_LIMIT, eip4844::calculate_excess_blob_gas, BlockWithSenders, Chain,
|
||||||
EMPTY_OMMER_ROOT_HASH, U256,
|
ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH,
|
||||||
|
U256,
|
||||||
};
|
};
|
||||||
use std::{sync::Arc, time::SystemTime};
|
use std::{sync::Arc, time::SystemTime};
|
||||||
|
|
||||||
@ -35,11 +39,122 @@ impl EthBeaconConsensus {
|
|||||||
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||||
Self { chain_spec }
|
Self { chain_spec }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates that the EIP-4844 header fields are correct with respect to the parent block. This
|
||||||
|
/// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and
|
||||||
|
/// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the
|
||||||
|
/// parent header fields.
|
||||||
|
pub fn validate_against_parent_4844(
|
||||||
|
header: &SealedHeader,
|
||||||
|
parent: &SealedHeader,
|
||||||
|
) -> Result<(), ConsensusError> {
|
||||||
|
// From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension):
|
||||||
|
//
|
||||||
|
// > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas
|
||||||
|
// > are evaluated as 0.
|
||||||
|
//
|
||||||
|
// This means in the first post-fork block, calculate_excess_blob_gas will return 0.
|
||||||
|
let parent_blob_gas_used = parent.blob_gas_used.unwrap_or(0);
|
||||||
|
let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or(0);
|
||||||
|
|
||||||
|
if header.blob_gas_used.is_none() {
|
||||||
|
return Err(ConsensusError::BlobGasUsedMissing)
|
||||||
|
}
|
||||||
|
let excess_blob_gas = header.excess_blob_gas.ok_or(ConsensusError::ExcessBlobGasMissing)?;
|
||||||
|
|
||||||
|
let expected_excess_blob_gas =
|
||||||
|
calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used);
|
||||||
|
if expected_excess_blob_gas != excess_blob_gas {
|
||||||
|
return Err(ConsensusError::ExcessBlobGasDiff {
|
||||||
|
diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas },
|
||||||
|
parent_excess_blob_gas,
|
||||||
|
parent_blob_gas_used,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the gas limit for consistency between parent and self headers.
|
||||||
|
///
|
||||||
|
/// The maximum allowable difference between self and parent gas limits is determined by the
|
||||||
|
/// parent's gas limit divided by the elasticity multiplier (1024).
|
||||||
|
fn validate_against_parent_gas_limit(
|
||||||
|
&self,
|
||||||
|
header: &SealedHeader,
|
||||||
|
parent: &SealedHeader,
|
||||||
|
) -> Result<(), ConsensusError> {
|
||||||
|
// Determine the parent gas limit, considering elasticity multiplier on the London fork.
|
||||||
|
let parent_gas_limit =
|
||||||
|
if self.chain_spec.fork(Hardfork::London).transitions_at_block(header.number) {
|
||||||
|
parent.gas_limit *
|
||||||
|
self.chain_spec
|
||||||
|
.base_fee_params_at_timestamp(header.timestamp)
|
||||||
|
.elasticity_multiplier as u64
|
||||||
|
} else {
|
||||||
|
parent.gas_limit
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for an increase in gas limit beyond the allowed threshold.
|
||||||
|
if header.gas_limit > parent_gas_limit {
|
||||||
|
if header.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 {
|
||||||
|
return Err(ConsensusError::GasLimitInvalidIncrease {
|
||||||
|
parent_gas_limit,
|
||||||
|
child_gas_limit: header.gas_limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for a decrease in gas limit beyond the allowed threshold.
|
||||||
|
else if parent_gas_limit - header.gas_limit >= parent_gas_limit / 1024 {
|
||||||
|
return Err(ConsensusError::GasLimitInvalidDecrease {
|
||||||
|
parent_gas_limit,
|
||||||
|
child_gas_limit: header.gas_limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Check if the self gas limit is below the minimum required limit.
|
||||||
|
else if header.gas_limit < MINIMUM_GAS_LIMIT {
|
||||||
|
return Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: header.gas_limit })
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Consensus for EthBeaconConsensus {
|
impl Consensus for EthBeaconConsensus {
|
||||||
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
|
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
|
||||||
validate_header_standalone(header, &self.chain_spec)?;
|
validate_header_gas(header)?;
|
||||||
|
validate_header_base_fee(header, &self.chain_spec)?;
|
||||||
|
|
||||||
|
// EIP-4895: Beacon chain push withdrawals as operations
|
||||||
|
if self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp) &&
|
||||||
|
header.withdrawals_root.is_none()
|
||||||
|
{
|
||||||
|
return Err(ConsensusError::WithdrawalsRootMissing)
|
||||||
|
} else if !self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp) &&
|
||||||
|
header.withdrawals_root.is_some()
|
||||||
|
{
|
||||||
|
return Err(ConsensusError::WithdrawalsRootUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures that EIP-4844 fields are valid once cancun is active.
|
||||||
|
if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
|
||||||
|
validate_4844_header_standalone(header)?;
|
||||||
|
} else if header.blob_gas_used.is_some() {
|
||||||
|
return Err(ConsensusError::BlobGasUsedUnexpected)
|
||||||
|
} else if header.excess_blob_gas.is_some() {
|
||||||
|
return Err(ConsensusError::ExcessBlobGasUnexpected)
|
||||||
|
} else if header.parent_beacon_block_root.is_some() {
|
||||||
|
return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.chain_spec.is_prague_active_at_timestamp(header.timestamp) {
|
||||||
|
if header.requests_root.is_none() {
|
||||||
|
return Err(ConsensusError::RequestsRootMissing)
|
||||||
|
}
|
||||||
|
} else if header.requests_root.is_some() {
|
||||||
|
return Err(ConsensusError::RequestsRootUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +163,21 @@ impl Consensus for EthBeaconConsensus {
|
|||||||
header: &SealedHeader,
|
header: &SealedHeader,
|
||||||
parent: &SealedHeader,
|
parent: &SealedHeader,
|
||||||
) -> Result<(), ConsensusError> {
|
) -> Result<(), ConsensusError> {
|
||||||
header.validate_against_parent(parent, &self.chain_spec).map_err(ConsensusError::from)?;
|
validate_against_parent_hash_number(header, parent)?;
|
||||||
|
|
||||||
|
validate_against_parent_timestamp(header, parent)?;
|
||||||
|
|
||||||
|
// TODO Check difficulty increment between parent and self
|
||||||
|
// Ace age did increment it by some formula that we need to follow.
|
||||||
|
self.validate_against_parent_gas_limit(header, parent)?;
|
||||||
|
|
||||||
|
validate_against_parent_eip1559_base_fee(header, parent, &self.chain_spec)?;
|
||||||
|
|
||||||
|
// ensure that the blob gas fields for this block
|
||||||
|
if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
|
||||||
|
Self::validate_against_parent_4844(header, parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,3 +256,97 @@ impl Consensus for EthBeaconConsensus {
|
|||||||
validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests)
|
validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use reth_primitives::{proofs, ChainSpecBuilder, B256};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn header_with_gas_limit(gas_limit: u64) -> SealedHeader {
|
||||||
|
let header = Header { gas_limit, ..Default::default() };
|
||||||
|
header.seal(B256::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_gas_limit_increase() {
|
||||||
|
let parent = header_with_gas_limit(1024 * 10);
|
||||||
|
let child = header_with_gas_limit(parent.gas_limit + 5);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
|
||||||
|
.validate_against_parent_gas_limit(&child, &parent),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gas_limit_below_minimum() {
|
||||||
|
let parent = header_with_gas_limit(MINIMUM_GAS_LIMIT);
|
||||||
|
let child = header_with_gas_limit(MINIMUM_GAS_LIMIT - 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
|
||||||
|
.validate_against_parent_gas_limit(&child, &parent),
|
||||||
|
Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: child.gas_limit })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_gas_limit_increase_exceeding_limit() {
|
||||||
|
let parent = header_with_gas_limit(1024 * 10);
|
||||||
|
let child = header_with_gas_limit(parent.gas_limit + parent.gas_limit / 1024 + 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
|
||||||
|
.validate_against_parent_gas_limit(&child, &parent),
|
||||||
|
Err(ConsensusError::GasLimitInvalidIncrease {
|
||||||
|
parent_gas_limit: parent.gas_limit,
|
||||||
|
child_gas_limit: child.gas_limit,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_gas_limit_decrease_within_limit() {
|
||||||
|
let parent = header_with_gas_limit(1024 * 10);
|
||||||
|
let child = header_with_gas_limit(parent.gas_limit - 5);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
|
||||||
|
.validate_against_parent_gas_limit(&child, &parent),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_gas_limit_decrease_exceeding_limit() {
|
||||||
|
let parent = header_with_gas_limit(1024 * 10);
|
||||||
|
let child = header_with_gas_limit(parent.gas_limit - parent.gas_limit / 1024 - 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
|
||||||
|
.validate_against_parent_gas_limit(&child, &parent),
|
||||||
|
Err(ConsensusError::GasLimitInvalidDecrease {
|
||||||
|
parent_gas_limit: parent.gas_limit,
|
||||||
|
child_gas_limit: child.gas_limit,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shanghai_block_zero_withdrawals() {
|
||||||
|
// ensures that if shanghai is activated, and we include a block with a withdrawals root,
|
||||||
|
// that the header is valid
|
||||||
|
let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());
|
||||||
|
|
||||||
|
let header = Header {
|
||||||
|
base_fee_per_gas: Some(1337u64),
|
||||||
|
withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.seal_slow();
|
||||||
|
|
||||||
|
assert_eq!(EthBeaconConsensus::new(chain_spec).validate_header(&header), Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,9 @@
|
|||||||
|
|
||||||
use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
|
use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
|
||||||
use reth_consensus_common::validation::{
|
use reth_consensus_common::validation::{
|
||||||
validate_block_pre_execution, validate_header_extradata, validate_header_standalone,
|
validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number,
|
||||||
|
validate_against_parent_timestamp, validate_block_pre_execution, validate_header_base_fee,
|
||||||
|
validate_header_extradata, validate_header_gas,
|
||||||
};
|
};
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
BlockWithSenders, ChainSpec, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256,
|
BlockWithSenders, ChainSpec, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256,
|
||||||
@ -44,8 +46,8 @@ impl OptimismBeaconConsensus {
|
|||||||
|
|
||||||
impl Consensus for OptimismBeaconConsensus {
|
impl Consensus for OptimismBeaconConsensus {
|
||||||
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
|
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
|
||||||
validate_header_standalone(header, &self.chain_spec)?;
|
validate_header_gas(header)?;
|
||||||
Ok(())
|
validate_header_base_fee(header, &self.chain_spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_header_against_parent(
|
fn validate_header_against_parent(
|
||||||
@ -53,7 +55,14 @@ impl Consensus for OptimismBeaconConsensus {
|
|||||||
header: &SealedHeader,
|
header: &SealedHeader,
|
||||||
parent: &SealedHeader,
|
parent: &SealedHeader,
|
||||||
) -> Result<(), ConsensusError> {
|
) -> Result<(), ConsensusError> {
|
||||||
header.validate_against_parent(parent, &self.chain_spec).map_err(ConsensusError::from)?;
|
validate_against_parent_hash_number(header, parent)?;
|
||||||
|
|
||||||
|
if self.chain_spec.is_bedrock_active_at_block(header.number) {
|
||||||
|
validate_against_parent_timestamp(header, parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_against_parent_eip1559_base_fee(header, parent, &self.chain_spec)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,10 @@
|
|||||||
use crate::block::{generate_valid_header, valid_header_strategy};
|
use crate::block::{generate_valid_header, valid_header_strategy};
|
||||||
use crate::{
|
use crate::{
|
||||||
basefee::calc_next_block_base_fee,
|
basefee::calc_next_block_base_fee,
|
||||||
constants,
|
constants::{ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH},
|
||||||
constants::{
|
|
||||||
ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH,
|
|
||||||
MINIMUM_GAS_LIMIT,
|
|
||||||
},
|
|
||||||
eip4844::{calc_blob_gasprice, calculate_excess_blob_gas},
|
eip4844::{calc_blob_gasprice, calculate_excess_blob_gas},
|
||||||
keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes,
|
keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes, B256,
|
||||||
ChainSpec, GotExpected, GotExpectedBoxed, Hardfork, B256, B64, U256,
|
B64, U256,
|
||||||
};
|
};
|
||||||
use alloy_rlp::{length_of_length, Decodable, Encodable};
|
use alloy_rlp::{length_of_length, Decodable, Encodable};
|
||||||
use bytes::BufMut;
|
use bytes::BufMut;
|
||||||
@ -517,92 +513,6 @@ impl Decodable for Header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that can occur during header sanity checks.
|
|
||||||
#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum HeaderValidationError {
|
|
||||||
/// Error when the block number does not match the parent block number.
|
|
||||||
#[error(
|
|
||||||
"block number {block_number} does not match parent block number {parent_block_number}"
|
|
||||||
)]
|
|
||||||
ParentBlockNumberMismatch {
|
|
||||||
/// The parent block number.
|
|
||||||
parent_block_number: BlockNumber,
|
|
||||||
/// The block number.
|
|
||||||
block_number: BlockNumber,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Error when the parent hash does not match the expected parent hash.
|
|
||||||
#[error("mismatched parent hash: {0}")]
|
|
||||||
ParentHashMismatch(GotExpectedBoxed<B256>),
|
|
||||||
|
|
||||||
/// Error when the block timestamp is in the past compared to the parent timestamp.
|
|
||||||
#[error("block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}")]
|
|
||||||
TimestampIsInPast {
|
|
||||||
/// The parent block's timestamp.
|
|
||||||
parent_timestamp: u64,
|
|
||||||
/// The block's timestamp.
|
|
||||||
timestamp: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Error when the base fee is missing.
|
|
||||||
#[error("base fee missing")]
|
|
||||||
BaseFeeMissing,
|
|
||||||
|
|
||||||
/// Error when the block's base fee is different from the expected base fee.
|
|
||||||
#[error("block base fee mismatch: {0}")]
|
|
||||||
BaseFeeDiff(GotExpected<u64>),
|
|
||||||
|
|
||||||
/// Error when the child gas limit exceeds the maximum allowed decrease.
|
|
||||||
#[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")]
|
|
||||||
GasLimitInvalidDecrease {
|
|
||||||
/// The parent gas limit.
|
|
||||||
parent_gas_limit: u64,
|
|
||||||
/// The child gas limit.
|
|
||||||
child_gas_limit: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Error when the child gas limit exceeds the maximum allowed increase.
|
|
||||||
#[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")]
|
|
||||||
GasLimitInvalidIncrease {
|
|
||||||
/// The parent gas limit.
|
|
||||||
parent_gas_limit: u64,
|
|
||||||
/// The child gas limit.
|
|
||||||
child_gas_limit: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Error indicating that the child gas limit is below the minimum allowed limit.
|
|
||||||
///
|
|
||||||
/// This error occurs when the child gas limit is less than the specified minimum gas limit.
|
|
||||||
#[error("child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})")]
|
|
||||||
GasLimitInvalidMinimum {
|
|
||||||
/// The child gas limit.
|
|
||||||
child_gas_limit: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Error when blob gas used is missing.
|
|
||||||
#[error("missing blob gas used")]
|
|
||||||
BlobGasUsedMissing,
|
|
||||||
|
|
||||||
/// Error when excess blob gas is missing.
|
|
||||||
#[error("missing excess blob gas")]
|
|
||||||
ExcessBlobGasMissing,
|
|
||||||
|
|
||||||
/// Error when there is an invalid excess blob gas.
|
|
||||||
#[error(
|
|
||||||
"invalid excess blob gas: {diff}; \
|
|
||||||
parent excess blob gas: {parent_excess_blob_gas}, \
|
|
||||||
parent blob gas used: {parent_blob_gas_used}"
|
|
||||||
)]
|
|
||||||
ExcessBlobGasDiff {
|
|
||||||
/// The excess blob gas diff.
|
|
||||||
diff: GotExpected<u64>,
|
|
||||||
/// The parent excess blob gas.
|
|
||||||
parent_excess_blob_gas: u64,
|
|
||||||
/// The parent blob gas used.
|
|
||||||
parent_blob_gas_used: u64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want
|
/// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want
|
||||||
/// to modify header.
|
/// to modify header.
|
||||||
#[main_codec(no_arbitrary)]
|
#[main_codec(no_arbitrary)]
|
||||||
@ -670,198 +580,6 @@ impl SealedHeader {
|
|||||||
self.header.difficulty = difficulty;
|
self.header.difficulty = difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks the gas limit for consistency between parent and self headers.
|
|
||||||
///
|
|
||||||
/// The maximum allowable difference between self and parent gas limits is determined by the
|
|
||||||
/// parent's gas limit divided by the elasticity multiplier (1024).
|
|
||||||
///
|
|
||||||
/// This check is skipped if the Optimism flag is enabled in the chain spec, as gas limits on
|
|
||||||
/// Optimism can adjust instantly.
|
|
||||||
#[inline(always)]
|
|
||||||
fn validate_gas_limit(
|
|
||||||
&self,
|
|
||||||
parent: &Self,
|
|
||||||
chain_spec: &ChainSpec,
|
|
||||||
) -> Result<(), HeaderValidationError> {
|
|
||||||
// Determine the parent gas limit, considering elasticity multiplier on the London fork.
|
|
||||||
let parent_gas_limit =
|
|
||||||
if chain_spec.fork(Hardfork::London).transitions_at_block(self.number) {
|
|
||||||
parent.gas_limit *
|
|
||||||
chain_spec.base_fee_params_at_timestamp(self.timestamp).elasticity_multiplier
|
|
||||||
as u64
|
|
||||||
} else {
|
|
||||||
parent.gas_limit
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check for an increase in gas limit beyond the allowed threshold.
|
|
||||||
if self.gas_limit > parent_gas_limit {
|
|
||||||
if self.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 {
|
|
||||||
return Err(HeaderValidationError::GasLimitInvalidIncrease {
|
|
||||||
parent_gas_limit,
|
|
||||||
child_gas_limit: self.gas_limit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for a decrease in gas limit beyond the allowed threshold.
|
|
||||||
else if parent_gas_limit - self.gas_limit >= parent_gas_limit / 1024 {
|
|
||||||
return Err(HeaderValidationError::GasLimitInvalidDecrease {
|
|
||||||
parent_gas_limit,
|
|
||||||
child_gas_limit: self.gas_limit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Check if the self gas limit is below the minimum required limit.
|
|
||||||
else if self.gas_limit < MINIMUM_GAS_LIMIT {
|
|
||||||
return Err(HeaderValidationError::GasLimitInvalidMinimum {
|
|
||||||
child_gas_limit: self.gas_limit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates the integrity and consistency of a sealed block header in relation to its parent
|
|
||||||
/// header.
|
|
||||||
///
|
|
||||||
/// This function checks various properties of the sealed header against its parent header and
|
|
||||||
/// the chain specification. It ensures that the block forms a valid and secure continuation
|
|
||||||
/// of the blockchain.
|
|
||||||
///
|
|
||||||
/// ## Arguments
|
|
||||||
///
|
|
||||||
/// * `parent` - The sealed header of the parent block.
|
|
||||||
/// * `chain_spec` - The chain specification providing configuration parameters for the
|
|
||||||
/// blockchain.
|
|
||||||
///
|
|
||||||
/// ## Errors
|
|
||||||
///
|
|
||||||
/// Returns a [`HeaderValidationError`] if any validation check fails, indicating specific
|
|
||||||
/// issues with the sealed header. The possible errors include mismatched block numbers,
|
|
||||||
/// parent hash mismatches, timestamp inconsistencies, gas limit violations, base fee
|
|
||||||
/// discrepancies (for EIP-1559), and errors related to the blob gas fields (EIP-4844).
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// Some checks, such as gas limit validation, are conditionally skipped based on the presence
|
|
||||||
/// of certain features (e.g., Optimism feature) or the activation of specific hardforks.
|
|
||||||
pub fn validate_against_parent(
|
|
||||||
&self,
|
|
||||||
parent: &Self,
|
|
||||||
chain_spec: &ChainSpec,
|
|
||||||
) -> Result<(), HeaderValidationError> {
|
|
||||||
// Parent number is consistent.
|
|
||||||
if parent.number + 1 != self.number {
|
|
||||||
return Err(HeaderValidationError::ParentBlockNumberMismatch {
|
|
||||||
parent_block_number: parent.number,
|
|
||||||
block_number: self.number,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if parent.hash != self.parent_hash {
|
|
||||||
return Err(HeaderValidationError::ParentHashMismatch(
|
|
||||||
GotExpected { got: self.parent_hash, expected: parent.hash }.into(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// timestamp in past check
|
|
||||||
#[cfg(feature = "optimism")]
|
|
||||||
if chain_spec.is_bedrock_active_at_block(self.header.number) &&
|
|
||||||
self.header.is_timestamp_in_past(parent.timestamp)
|
|
||||||
{
|
|
||||||
return Err(HeaderValidationError::TimestampIsInPast {
|
|
||||||
parent_timestamp: parent.timestamp,
|
|
||||||
timestamp: self.timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "optimism"))]
|
|
||||||
if self.header.is_timestamp_in_past(parent.timestamp) {
|
|
||||||
return Err(HeaderValidationError::TimestampIsInPast {
|
|
||||||
parent_timestamp: parent.timestamp,
|
|
||||||
timestamp: self.timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Check difficulty increment between parent and self
|
|
||||||
// Ace age did increment it by some formula that we need to follow.
|
|
||||||
|
|
||||||
if cfg!(feature = "optimism") {
|
|
||||||
// On Optimism, the gas limit can adjust instantly, so we skip this check
|
|
||||||
// if the optimism feature is enabled in the chain spec.
|
|
||||||
if !chain_spec.is_optimism() {
|
|
||||||
self.validate_gas_limit(parent, chain_spec)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.validate_gas_limit(parent, chain_spec)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EIP-1559 check base fee
|
|
||||||
if chain_spec.fork(Hardfork::London).active_at_block(self.number) {
|
|
||||||
let base_fee = self.base_fee_per_gas.ok_or(HeaderValidationError::BaseFeeMissing)?;
|
|
||||||
|
|
||||||
let expected_base_fee = if chain_spec
|
|
||||||
.fork(Hardfork::London)
|
|
||||||
.transitions_at_block(self.number)
|
|
||||||
{
|
|
||||||
constants::EIP1559_INITIAL_BASE_FEE
|
|
||||||
} else {
|
|
||||||
// This BaseFeeMissing will not happen as previous blocks are checked to have
|
|
||||||
// them.
|
|
||||||
parent
|
|
||||||
.next_block_base_fee(chain_spec.base_fee_params_at_timestamp(self.timestamp))
|
|
||||||
.ok_or(HeaderValidationError::BaseFeeMissing)?
|
|
||||||
};
|
|
||||||
if expected_base_fee != base_fee {
|
|
||||||
return Err(HeaderValidationError::BaseFeeDiff(GotExpected {
|
|
||||||
expected: expected_base_fee,
|
|
||||||
got: base_fee,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure that the blob gas fields for this block
|
|
||||||
if chain_spec.is_cancun_active_at_timestamp(self.timestamp) {
|
|
||||||
self.validate_4844_header_against_parent(parent)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates that the EIP-4844 header fields are correct with respect to the parent block. This
|
|
||||||
/// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and
|
|
||||||
/// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the
|
|
||||||
/// parent header fields.
|
|
||||||
pub fn validate_4844_header_against_parent(
|
|
||||||
&self,
|
|
||||||
parent: &Self,
|
|
||||||
) -> Result<(), HeaderValidationError> {
|
|
||||||
// From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension):
|
|
||||||
//
|
|
||||||
// > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas
|
|
||||||
// > are evaluated as 0.
|
|
||||||
//
|
|
||||||
// This means in the first post-fork block, calculate_excess_blob_gas will return 0.
|
|
||||||
let parent_blob_gas_used = parent.blob_gas_used.unwrap_or(0);
|
|
||||||
let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or(0);
|
|
||||||
|
|
||||||
if self.blob_gas_used.is_none() {
|
|
||||||
return Err(HeaderValidationError::BlobGasUsedMissing)
|
|
||||||
}
|
|
||||||
let excess_blob_gas =
|
|
||||||
self.excess_blob_gas.ok_or(HeaderValidationError::ExcessBlobGasMissing)?;
|
|
||||||
|
|
||||||
let expected_excess_blob_gas =
|
|
||||||
calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used);
|
|
||||||
if expected_excess_blob_gas != excess_blob_gas {
|
|
||||||
return Err(HeaderValidationError::ExcessBlobGasDiff {
|
|
||||||
diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas },
|
|
||||||
parent_excess_blob_gas,
|
|
||||||
parent_blob_gas_used,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract raw header that can be modified.
|
/// Extract raw header that can be modified.
|
||||||
pub fn unseal(self) -> Header {
|
pub fn unseal(self) -> Header {
|
||||||
self.header
|
self.header
|
||||||
@ -1035,10 +753,7 @@ impl From<HeadersDirection> for bool {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Bytes, Decodable, Encodable, Header, B256};
|
use super::{Bytes, Decodable, Encodable, Header, B256};
|
||||||
use crate::{
|
use crate::{address, b256, bloom, bytes, hex, Address, HeadersDirection, U256};
|
||||||
address, b256, bloom, bytes, header::MINIMUM_GAS_LIMIT, hex, Address, ChainSpec,
|
|
||||||
HeaderValidationError, HeadersDirection, SealedHeader, U256,
|
|
||||||
};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
// Test vector from: https://eips.ethereum.org/EIPS/eip-2481
|
// Test vector from: https://eips.ethereum.org/EIPS/eip-2481
|
||||||
@ -1301,103 +1016,4 @@ mod tests {
|
|||||||
Header::decode(&mut data.as_slice())
|
Header::decode(&mut data.as_slice())
|
||||||
.expect_err("blob_gas_used size should make this header decoding fail");
|
.expect_err("blob_gas_used size should make this header decoding fail");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_valid_gas_limit_increase() {
|
|
||||||
let parent = SealedHeader {
|
|
||||||
header: Header { gas_limit: 1024 * 10, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let child = SealedHeader {
|
|
||||||
header: Header { gas_limit: parent.header.gas_limit + 5, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let chain_spec = ChainSpec::default();
|
|
||||||
|
|
||||||
assert_eq!(child.validate_gas_limit(&parent, &chain_spec), Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gas_limit_below_minimum() {
|
|
||||||
let parent = SealedHeader {
|
|
||||||
header: Header { gas_limit: MINIMUM_GAS_LIMIT, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let child = SealedHeader {
|
|
||||||
header: Header { gas_limit: MINIMUM_GAS_LIMIT - 1, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let chain_spec = ChainSpec::default();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
child.validate_gas_limit(&parent, &chain_spec),
|
|
||||||
Err(HeaderValidationError::GasLimitInvalidMinimum { child_gas_limit: child.gas_limit })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_gas_limit_increase_exceeding_limit() {
|
|
||||||
let gas_limit = 1024 * 10;
|
|
||||||
let parent = SealedHeader {
|
|
||||||
header: Header { gas_limit, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let child = SealedHeader {
|
|
||||||
header: Header {
|
|
||||||
gas_limit: parent.header.gas_limit + parent.header.gas_limit / 1024 + 1,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let chain_spec = ChainSpec::default();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
child.validate_gas_limit(&parent, &chain_spec),
|
|
||||||
Err(HeaderValidationError::GasLimitInvalidIncrease {
|
|
||||||
parent_gas_limit: parent.header.gas_limit,
|
|
||||||
child_gas_limit: child.header.gas_limit,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_valid_gas_limit_decrease_within_limit() {
|
|
||||||
let gas_limit = 1024 * 10;
|
|
||||||
let parent = SealedHeader {
|
|
||||||
header: Header { gas_limit, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let child = SealedHeader {
|
|
||||||
header: Header { gas_limit: parent.header.gas_limit - 5, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let chain_spec = ChainSpec::default();
|
|
||||||
|
|
||||||
assert_eq!(child.validate_gas_limit(&parent, &chain_spec), Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_gas_limit_decrease_exceeding_limit() {
|
|
||||||
let gas_limit = 1024 * 10;
|
|
||||||
let parent = SealedHeader {
|
|
||||||
header: Header { gas_limit, ..Default::default() },
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let child = SealedHeader {
|
|
||||||
header: Header {
|
|
||||||
gas_limit: parent.header.gas_limit - parent.header.gas_limit / 1024 - 1,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let chain_spec = ChainSpec::default();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
child.validate_gas_limit(&parent, &chain_spec),
|
|
||||||
Err(HeaderValidationError::GasLimitInvalidDecrease {
|
|
||||||
parent_gas_limit: parent.header.gas_limit,
|
|
||||||
child_gas_limit: child.header.gas_limit,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ pub use constants::{
|
|||||||
};
|
};
|
||||||
pub use error::{GotExpected, GotExpectedBoxed};
|
pub use error::{GotExpected, GotExpectedBoxed};
|
||||||
pub use genesis::{ChainConfig, Genesis, GenesisAccount};
|
pub use genesis::{ChainConfig, Genesis, GenesisAccount};
|
||||||
pub use header::{Header, HeaderValidationError, HeadersDirection, SealedHeader};
|
pub use header::{Header, HeadersDirection, SealedHeader};
|
||||||
pub use integer_list::IntegerList;
|
pub use integer_list::IntegerList;
|
||||||
pub use log::{logs_bloom, Log};
|
pub use log::{logs_bloom, Log};
|
||||||
pub use net::{
|
pub use net::{
|
||||||
|
|||||||
Reference in New Issue
Block a user