chore: move header validation from reth-primitives to consensus crates (#8802)

This commit is contained in:
joshieDo
2024-06-14 13:21:45 +02:00
committed by GitHub
parent f3bd255007
commit 68e902db2e
6 changed files with 390 additions and 457 deletions

View File

@ -9,56 +9,29 @@ use reth_primitives::{
ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader,
};
/// Validate header standalone
pub fn validate_header_standalone(
header: &SealedHeader,
chain_spec: &ChainSpec,
) -> Result<(), ConsensusError> {
// Gas used needs to be less than gas limit. Gas used is going to be checked after execution.
/// Gas used needs to be less than gas limit. Gas used is going to be checked after execution.
#[inline]
pub fn validate_header_gas(header: &SealedHeader) -> Result<(), ConsensusError> {
if header.gas_used > header.gas_limit {
return Err(ConsensusError::HeaderGasUsedExceedsGasLimit {
gas_used: header.gas_used,
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) &&
header.base_fee_per_gas.is_none()
{
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(())
}
@ -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.
/// This must be 32 bytes or fewer; formally Hx.
#[inline]
pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError> {
if header.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
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)]
mod tests {
use super::*;
@ -410,22 +456,6 @@ mod tests {
.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]
fn cancun_block_incorrect_blob_gas_used() {
let chain_spec = ChainSpecBuilder::mainnet().cancun_activated().build();

View File

@ -10,8 +10,8 @@
#![cfg_attr(not(feature = "std"), no_std)]
use reth_primitives::{
BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected, GotExpectedBoxed, Header,
HeaderValidationError, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader,
constants::MINIMUM_GAS_LIMIT, BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected,
GotExpectedBoxed, Header, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader,
B256, U256,
};
@ -206,6 +206,10 @@ pub enum ConsensusError {
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("block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}")]
TimestampIsInFuture {
@ -329,9 +333,60 @@ pub enum ConsensusError {
#[error(transparent)]
InvalidTransaction(#[from] InvalidTransactionError),
/// Error type transparently wrapping `HeaderValidationError`.
#[error(transparent)]
HeaderValidationError(#[from] HeaderValidationError),
/// 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 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 {

View File

@ -10,11 +10,15 @@
use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
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::{
BlockWithSenders, Chain, ChainSpec, Hardfork, Header, SealedBlock, SealedHeader,
EMPTY_OMMER_ROOT_HASH, U256,
constants::MINIMUM_GAS_LIMIT, eip4844::calculate_excess_blob_gas, BlockWithSenders, Chain,
ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH,
U256,
};
use std::{sync::Arc, time::SystemTime};
@ -35,11 +39,122 @@ impl EthBeaconConsensus {
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
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 {
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(())
}
@ -48,7 +163,21 @@ impl Consensus for EthBeaconConsensus {
header: &SealedHeader,
parent: &SealedHeader,
) -> 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(())
}
@ -127,3 +256,97 @@ impl Consensus for EthBeaconConsensus {
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(()));
}
}

View File

@ -11,7 +11,9 @@
use reth_consensus::{Consensus, ConsensusError, PostExecutionInput};
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::{
BlockWithSenders, ChainSpec, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256,
@ -44,8 +46,8 @@ impl OptimismBeaconConsensus {
impl Consensus for OptimismBeaconConsensus {
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
validate_header_standalone(header, &self.chain_spec)?;
Ok(())
validate_header_gas(header)?;
validate_header_base_fee(header, &self.chain_spec)
}
fn validate_header_against_parent(
@ -53,7 +55,14 @@ impl Consensus for OptimismBeaconConsensus {
header: &SealedHeader,
parent: &SealedHeader,
) -> 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(())
}

View File

@ -2,14 +2,10 @@
use crate::block::{generate_valid_header, valid_header_strategy};
use crate::{
basefee::calc_next_block_base_fee,
constants,
constants::{
ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH,
MINIMUM_GAS_LIMIT,
},
constants::{ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH},
eip4844::{calc_blob_gasprice, calculate_excess_blob_gas},
keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes,
ChainSpec, GotExpected, GotExpectedBoxed, Hardfork, B256, B64, U256,
keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes, B256,
B64, U256,
};
use alloy_rlp::{length_of_length, Decodable, Encodable};
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
/// to modify header.
#[main_codec(no_arbitrary)]
@ -670,198 +580,6 @@ impl SealedHeader {
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.
pub fn unseal(self) -> Header {
self.header
@ -1035,10 +753,7 @@ impl From<HeadersDirection> for bool {
#[cfg(test)]
mod tests {
use super::{Bytes, Decodable, Encodable, Header, B256};
use crate::{
address, b256, bloom, bytes, header::MINIMUM_GAS_LIMIT, hex, Address, ChainSpec,
HeaderValidationError, HeadersDirection, SealedHeader, U256,
};
use crate::{address, b256, bloom, bytes, hex, Address, HeadersDirection, U256};
use std::str::FromStr;
// Test vector from: https://eips.ethereum.org/EIPS/eip-2481
@ -1301,103 +1016,4 @@ mod tests {
Header::decode(&mut data.as_slice())
.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,
})
);
}
}

View File

@ -63,7 +63,7 @@ pub use constants::{
};
pub use error::{GotExpected, GotExpectedBoxed};
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 log::{logs_bloom, Log};
pub use net::{