//! Consensus protocol functions
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
use reth_primitives::{
constants::MINIMUM_GAS_LIMIT, BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected,
GotExpectedBoxed, Header, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader,
B256, U256,
};
#[cfg(feature = "std")]
use std::fmt::Debug;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{fmt::Debug, vec::Vec};
/// A consensus implementation that does nothing.
pub mod noop;
#[cfg(any(test, feature = "test-utils"))]
/// test helpers for mocking consensus
pub mod test_utils;
/// Post execution input passed to [`Consensus::validate_block_post_execution`].
#[derive(Debug)]
pub struct PostExecutionInput<'a> {
/// Receipts of the block.
pub receipts: &'a [Receipt],
/// EIP-7685 requests of the block.
pub requests: &'a [Request],
}
impl<'a> PostExecutionInput<'a> {
/// Creates a new instance of `PostExecutionInput`.
pub const fn new(receipts: &'a [Receipt], requests: &'a [Request]) -> Self {
Self { receipts, requests }
}
}
/// Consensus is a protocol that chooses canonical chain.
#[auto_impl::auto_impl(&, Arc)]
pub trait Consensus: Debug + Send + Sync {
/// Validate if header is correct and follows consensus specification.
///
/// This is called on standalone header to check if all hashes are correct.
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError>;
/// Validate that the header information regarding parent are correct.
/// This checks the block number, timestamp, basefee and gas limit increment.
///
/// This is called before properties that are not in the header itself (like total difficulty)
/// have been computed.
///
/// **This should not be called for the genesis block**.
///
/// Note: Validating header against its parent does not include other Consensus validations.
fn validate_header_against_parent(
&self,
header: &SealedHeader,
parent: &SealedHeader,
) -> Result<(), ConsensusError>;
/// Validates the given headers
///
/// This ensures that the first header is valid on its own and all subsequent headers are valid
/// on its own and valid against its parent.
///
/// Note: this expects that the headers are in natural order (ascending block number)
fn validate_header_range(&self, headers: &[SealedHeader]) -> Result<(), HeaderConsensusError> {
if let Some((initial_header, remaining_headers)) = headers.split_first() {
self.validate_header(initial_header)
.map_err(|e| HeaderConsensusError(e, initial_header.clone()))?;
let mut parent = initial_header;
for child in remaining_headers {
self.validate_header(child).map_err(|e| HeaderConsensusError(e, child.clone()))?;
self.validate_header_against_parent(child, parent)
.map_err(|e| HeaderConsensusError(e, child.clone()))?;
parent = child;
}
}
Ok(())
}
/// Validate if the header is correct and follows the consensus specification, including
/// computed properties (like total difficulty).
///
/// Some consensus engines may want to do additional checks here.
///
/// Note: validating headers with TD does not include other Consensus validation.
fn validate_header_with_total_difficulty(
&self,
header: &Header,
total_difficulty: U256,
) -> Result<(), ConsensusError>;
/// Validate a block disregarding world state, i.e. things that can be checked before sender
/// recovery and execution.
///
/// See the Yellow Paper sections 4.3.2 "Holistic Validity", 4.3.4 "Block Header Validity", and
/// 11.1 "Ommer Validation".
///
/// **This should not be called for the genesis block**.
///
/// Note: validating blocks does not include other validations of the Consensus
fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), ConsensusError>;
/// Validate a block considering world state, i.e. things that can not be checked before
/// execution.
///
/// See the Yellow Paper sections 4.3.2 "Holistic Validity".
///
/// Note: validating blocks does not include other validations of the Consensus
fn validate_block_post_execution(
&self,
block: &BlockWithSenders,
input: PostExecutionInput<'_>,
) -> Result<(), ConsensusError>;
}
/// Consensus Errors
#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)]
pub enum ConsensusError {
/// Error when the gas used in the header exceeds the gas limit.
#[display(fmt = "block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
HeaderGasUsedExceedsGasLimit {
/// The gas used in the block header.
gas_used: u64,
/// The gas limit in the block header.
gas_limit: u64,
},
/// Error when block gas used doesn't match expected value
#[display(
fmt = "block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}"
)]
BlockGasUsed {
/// The gas diff.
gas: GotExpected,
/// Gas spent by each transaction
gas_spent_by_tx: Vec<(u64, u64)>,
},
/// Error when the hash of block ommer is different from the expected hash.
#[display(fmt = "mismatched block ommer hash: {_0}")]
BodyOmmersHashDiff(GotExpectedBoxed),
/// Error when the state root in the block is different from the expected state root.
#[display(fmt = "mismatched block state root: {_0}")]
BodyStateRootDiff(GotExpectedBoxed),
/// Error when the transaction root in the block is different from the expected transaction
/// root.
#[display(fmt = "mismatched block transaction root: {_0}")]
BodyTransactionRootDiff(GotExpectedBoxed),
/// Error when the receipt root in the block is different from the expected receipt root.
#[display(fmt = "receipt root mismatch: {_0}")]
BodyReceiptRootDiff(GotExpectedBoxed),
/// Error when header bloom filter is different from the expected bloom filter.
#[display(fmt = "header bloom filter mismatch: {_0}")]
BodyBloomLogDiff(GotExpectedBoxed),
/// Error when the withdrawals root in the block is different from the expected withdrawals
/// root.
#[display(fmt = "mismatched block withdrawals root: {_0}")]
BodyWithdrawalsRootDiff(GotExpectedBoxed),
/// Error when the requests root in the block is different from the expected requests
/// root.
#[display(fmt = "mismatched block requests root: {_0}")]
BodyRequestsRootDiff(GotExpectedBoxed),
/// Error when a block with a specific hash and number is already known.
#[display(fmt = "block with [hash={hash}, number={number}] is already known")]
BlockKnown {
/// The hash of the known block.
hash: BlockHash,
/// The block number of the known block.
number: BlockNumber,
},
/// Error when the parent hash of a block is not known.
#[display(fmt = "block parent [hash={hash}] is not known")]
ParentUnknown {
/// The hash of the unknown parent block.
hash: BlockHash,
},
/// Error when the block number does not match the parent block number.
#[display(
fmt = "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.
#[display(fmt = "mismatched parent hash: {_0}")]
ParentHashMismatch(GotExpectedBoxed),
/// Error when the block timestamp is in the future compared to our clock time.
#[display(
fmt = "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
)]
TimestampIsInFuture {
/// The block's timestamp.
timestamp: u64,
/// The current timestamp.
present_timestamp: u64,
},
/// Error when the base fee is missing.
#[display(fmt = "base fee missing")]
BaseFeeMissing,
/// Error when there is a transaction signer recovery error.
#[display(fmt = "transaction signer recovery error")]
TransactionSignerRecoveryError,
/// Error when the extra data length exceeds the maximum allowed.
#[display(fmt = "extra data {len} exceeds max length")]
ExtraDataExceedsMax {
/// The length of the extra data.
len: usize,
},
/// Error when the difficulty after a merge is not zero.
#[display(fmt = "difficulty after merge is not zero")]
TheMergeDifficultyIsNotZero,
/// Error when the nonce after a merge is not zero.
#[display(fmt = "nonce after merge is not zero")]
TheMergeNonceIsNotZero,
/// Error when the ommer root after a merge is not empty.
#[display(fmt = "ommer root after merge is not empty")]
TheMergeOmmerRootIsNotEmpty,
/// Error when the withdrawals root is missing.
#[display(fmt = "missing withdrawals root")]
WithdrawalsRootMissing,
/// Error when the requests root is missing.
#[display(fmt = "missing requests root")]
RequestsRootMissing,
/// Error when an unexpected withdrawals root is encountered.
#[display(fmt = "unexpected withdrawals root")]
WithdrawalsRootUnexpected,
/// Error when an unexpected requests root is encountered.
#[display(fmt = "unexpected requests root")]
RequestsRootUnexpected,
/// Error when withdrawals are missing.
#[display(fmt = "missing withdrawals")]
BodyWithdrawalsMissing,
/// Error when requests are missing.
#[display(fmt = "missing requests")]
BodyRequestsMissing,
/// Error when blob gas used is missing.
#[display(fmt = "missing blob gas used")]
BlobGasUsedMissing,
/// Error when unexpected blob gas used is encountered.
#[display(fmt = "unexpected blob gas used")]
BlobGasUsedUnexpected,
/// Error when excess blob gas is missing.
#[display(fmt = "missing excess blob gas")]
ExcessBlobGasMissing,
/// Error when unexpected excess blob gas is encountered.
#[display(fmt = "unexpected excess blob gas")]
ExcessBlobGasUnexpected,
/// Error when the parent beacon block root is missing.
#[display(fmt = "missing parent beacon block root")]
ParentBeaconBlockRootMissing,
/// Error when an unexpected parent beacon block root is encountered.
#[display(fmt = "unexpected parent beacon block root")]
ParentBeaconBlockRootUnexpected,
/// Error when blob gas used exceeds the maximum allowed.
#[display(
fmt = "blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}"
)]
BlobGasUsedExceedsMaxBlobGasPerBlock {
/// The actual blob gas used.
blob_gas_used: u64,
/// The maximum allowed blob gas per block.
max_blob_gas_per_block: u64,
},
/// Error when blob gas used is not a multiple of blob gas per blob.
#[display(
fmt = "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
)]
BlobGasUsedNotMultipleOfBlobGasPerBlob {
/// The actual blob gas used.
blob_gas_used: u64,
/// The blob gas per blob.
blob_gas_per_blob: u64,
},
/// Error when excess blob gas is not a multiple of blob gas per blob.
#[display(
fmt = "excess blob gas {excess_blob_gas} is not a multiple of blob gas per blob {blob_gas_per_blob}"
)]
ExcessBlobGasNotMultipleOfBlobGasPerBlob {
/// The actual excess blob gas.
excess_blob_gas: u64,
/// The blob gas per blob.
blob_gas_per_blob: u64,
},
/// Error when the blob gas used in the header does not match the expected blob gas used.
#[display(fmt = "blob gas used mismatch: {_0}")]
BlobGasUsedDiff(GotExpected),
/// Error for a transaction that violates consensus.
InvalidTransaction(InvalidTransactionError),
/// Error when the block's base fee is different from the expected base fee.
#[display(fmt = "block base fee mismatch: {_0}")]
BaseFeeDiff(GotExpected),
/// Error when there is an invalid excess blob gas.
#[display(fmt = "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,
/// 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.
#[display(fmt = "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.
#[display(
fmt = "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.
#[display(fmt = "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.
#[display(
fmt = "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,
},
}
#[cfg(feature = "std")]
impl std::error::Error for ConsensusError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidTransaction(err) => std::error::Error::source(err),
_ => Option::None,
}
}
}
impl ConsensusError {
/// Returns `true` if the error is a state root error.
pub const fn is_state_root_error(&self) -> bool {
matches!(self, Self::BodyStateRootDiff(_))
}
}
impl From for ConsensusError {
fn from(value: InvalidTransactionError) -> Self {
Self::InvalidTransaction(value)
}
}
/// `HeaderConsensusError` combines a `ConsensusError` with the `SealedHeader` it relates to.
#[derive(derive_more::Display, Debug)]
#[display(fmt = "Consensus error: {_0}, Invalid header: {_1:?}")]
pub struct HeaderConsensusError(ConsensusError, SealedHeader);
#[cfg(feature = "std")]
impl std::error::Error for HeaderConsensusError {}