//! Consensus protocol functions
//!
//! # Trait hierarchy
//!
//! Consensus validation is split across three traits, each adding a layer:
//!
//! - [`HeaderValidator`] — validates a header in isolation and against its parent. Used early in
//!   the validation pipeline before block execution.
//!
//! - [`Consensus`] — extends `HeaderValidator` with block body validation. Checks that the body
//!   matches the header (tx root, ommer hash, withdrawals) and runs pre-execution checks. Used
//!   before a block is executed.
//!
//! - [`FullConsensus`] — extends `Consensus` with post-execution validation. Checks execution
//!   results against the header (gas used, receipt root, logs bloom). Used after block execution to
//!   verify the outcome.
//!
//! In the engine, these are applied in order during payload validation (`engine_newPayload`).
//! Payload attribute validation for block building (`engine_forkchoiceUpdated`) is handled
//! separately at the engine API layer and does not use these traits.

#![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))]
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;

use alloc::{
    boxed::Box,
    fmt::Debug,
    string::{String, ToString},
    sync::Arc,
    vec::Vec,
};
use alloy_consensus::Header;
use alloy_eip7928::BlockAccessListGasError;
use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
use core::{error::Error, fmt::Display};

/// Pre-computed receipt root and logs bloom.
///
/// When provided to [`FullConsensus::validate_block_post_execution`], this allows skipping
/// the receipt root computation and using the pre-computed values instead.
pub type ReceiptRootBloom = (B256, Bloom);

/// Pre-computed transaction root.
///
/// When provided to [`Consensus::validate_block_pre_execution_with_tx_root`], this allows
/// skipping transaction trie reconstruction from the block body.
pub type TransactionRoot = B256;
use reth_execution_types::BlockExecutionResult;
use reth_primitives_traits::{
    constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
    transaction::error::InvalidTransactionError,
    Block, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock,
    SealedHeader,
};

/// A consensus implementation that does nothing.
pub mod noop;

#[cfg(any(test, feature = "test-utils"))]
/// test helpers for mocking consensus
pub mod test_utils;

/// [`Consensus`] implementation which knows full node primitives and is able to validation block's
/// execution outcome.
#[auto_impl::auto_impl(&, Arc)]
pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
    /// 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".
    ///
    /// If `receipt_root_bloom` is provided, the implementation should use the pre-computed
    /// receipt root and logs bloom instead of computing them from the receipts.
    ///
    /// Note: validating blocks does not include other validations of the Consensus
    fn validate_block_post_execution(
        &self,
        block: &RecoveredBlock<N::Block>,
        result: &BlockExecutionResult<N::Receipt>,
        receipt_root_bloom: Option<ReceiptRootBloom>,
        block_access_list_hash: Option<B256>,
    ) -> Result<(), ConsensusError>;
}

/// Consensus is a protocol that chooses canonical chain.
#[auto_impl::auto_impl(&, Arc)]
pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
    /// Ensures that body field values match the header.
    fn validate_body_against_header(
        &self,
        body: &B::Body,
        header: &SealedHeader<B::Header>,
    ) -> 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.4.2 "Holistic Validity", 4.4.4 "Block Header Validity".
    /// Note: Ommer Validation (previously section 11.1) has been deprecated since the Paris hard
    /// fork transition to proof of stake.
    ///
    /// **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<B>) -> Result<(), ConsensusError>;

    /// Returns `true` if the given consensus error is transient and may resolve on its own.
    ///
    /// On fast chains, clock skew between nodes can cause a valid block's timestamp to
    /// appear briefly in the future. Caching such blocks as permanently invalid would
    /// prevent them from being re-validated once the local clock catches up.
    ///
    /// Transient errors will not cause the block hash to be cached as permanently invalid,
    /// allowing the block to be re-validated later.
    fn is_transient_error(&self, _error: &ConsensusError) -> bool {
        false
    }

    /// Validate a block disregarding world state using an optional pre-computed transaction root.
    ///
    /// If `transaction_root` is provided, the implementation should use the pre-computed
    /// transaction root instead of recomputing it from the block body. The value must have been
    /// derived from `block.body().calculate_tx_root()`.
    ///
    /// By default this falls back to [`Self::validate_block_pre_execution`].
    fn validate_block_pre_execution_with_tx_root(
        &self,
        block: &SealedBlock<B>,
        transaction_root: Option<TransactionRoot>,
    ) -> Result<(), ConsensusError> {
        let _ = transaction_root;
        self.validate_block_pre_execution(block)
    }
}

/// `HeaderValidator` is a protocol that validates headers and their relationships.
#[auto_impl::auto_impl(&, Arc)]
pub trait HeaderValidator<H = Header>: 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<H>) -> 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 `HeaderValidator`
    /// validations.
    fn validate_header_against_parent(
        &self,
        header: &SealedHeader<H>,
        parent: &SealedHeader<H>,
    ) -> 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<H>],
    ) -> Result<(), HeaderConsensusError<H>>
    where
        H: Clone,
    {
        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(())
    }
}

/// Consensus Errors
#[derive(Debug, Clone, thiserror::Error)]
pub enum ConsensusError {
    /// Error when the gas used in the header exceeds the gas limit.
    #[error("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 the gas limit is more than the maximum allowed.
    #[error(
        "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
    )]
    HeaderGasLimitExceedsMax {
        /// The gas limit in the block header.
        gas_limit: u64,
    },

    /// Error when block gas used doesn't match expected value
    #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
    BlockGasUsed {
        /// The gas diff.
        gas: GotExpected<u64>,
        /// 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.
    #[error("mismatched block ommer hash: {0}")]
    BodyOmmersHashDiff(GotExpectedBoxed<B256>),

    /// Error when the state root in the block is different from the expected state root.
    #[error("mismatched block state root: {0}")]
    BodyStateRootDiff(GotExpectedBoxed<B256>),

    /// Error when the transaction root in the block is different from the expected transaction
    /// root.
    #[error("mismatched block transaction root: {0}")]
    BodyTransactionRootDiff(GotExpectedBoxed<B256>),

    /// Error when the receipt root in the block is different from the expected receipt root.
    #[error("receipt root mismatch: {0}")]
    BodyReceiptRootDiff(GotExpectedBoxed<B256>),

    /// Error when header bloom filter is different from the expected bloom filter.
    #[error("header bloom filter mismatch: {0}")]
    BodyBloomLogDiff(GotExpectedBoxed<Bloom>),

    /// Error when the withdrawals root in the block is different from the expected withdrawals
    /// root.
    #[error("mismatched block withdrawals root: {0}")]
    BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),

    /// Error when the requests hash in the block is different from the expected requests
    /// hash.
    #[error("mismatched block requests hash: {0}")]
    BodyRequestsHashDiff(GotExpectedBoxed<B256>),

    /// Error when a block with a specific hash and number is already known.
    #[error("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.
    #[error("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.
    #[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 future compared to our clock time.
    #[error(
        "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.
    #[error("base fee missing")]
    BaseFeeMissing,

    /// Error when there is a transaction signer recovery error.
    #[error("transaction signer recovery error")]
    TransactionSignerRecoveryError,

    /// Error when the extra data length exceeds the maximum allowed.
    #[error("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.
    #[error("difficulty after merge is not zero")]
    TheMergeDifficultyIsNotZero,

    /// Error when the nonce after a merge is not zero.
    #[error("nonce after merge is not zero")]
    TheMergeNonceIsNotZero,

    /// Error when the ommer root after a merge is not empty.
    #[error("ommer root after merge is not empty")]
    TheMergeOmmerRootIsNotEmpty,

    /// Error when the withdrawals root is missing.
    #[error("missing withdrawals root")]
    WithdrawalsRootMissing,

    /// Error when the requests hash is missing.
    #[error("missing requests hash")]
    RequestsHashMissing,

    /// Error when the block access list hash is missing.
    #[error("missing block access list hash")]
    BlockAccessListHashMissing,

    /// Error when the slot number is missing.
    #[error("missing slot number")]
    SlotNumberMissing,

    /// Error when an unexpected withdrawals root is encountered.
    #[error("unexpected withdrawals root")]
    WithdrawalsRootUnexpected,

    /// Error when an unexpected requests hash is encountered.
    #[error("unexpected requests hash")]
    RequestsHashUnexpected,

    /// Error when an unexpected block access list hash is encountered.
    #[error("unexpected block access list hash")]
    BlockAccessListHashUnexpected,

    /// Error when an unexpected slot number is encountered.
    #[error("unexpected slot number")]
    SlotNumberUnexpected,

    /// Error when withdrawals are missing.
    #[error("missing withdrawals")]
    BodyWithdrawalsMissing,

    /// Error when requests are missing.
    #[error("missing requests")]
    BodyRequestsMissing,

    /// Error when blob gas used is missing.
    #[error("missing blob gas used")]
    BlobGasUsedMissing,

    /// Error when unexpected blob gas used is encountered.
    #[error("unexpected blob gas used")]
    BlobGasUsedUnexpected,

    /// Error when excess blob gas is missing.
    #[error("missing excess blob gas")]
    ExcessBlobGasMissing,

    /// Error when unexpected excess blob gas is encountered.
    #[error("unexpected excess blob gas")]
    ExcessBlobGasUnexpected,

    /// Error when the parent beacon block root is missing.
    #[error("missing parent beacon block root")]
    ParentBeaconBlockRootMissing,

    /// Error when an unexpected parent beacon block root is encountered.
    #[error("unexpected parent beacon block root")]
    ParentBeaconBlockRootUnexpected,

    /// Error when blob gas used exceeds the maximum allowed.
    #[error("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.
    #[error(
        "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 the blob gas used in the header does not match the expected blob gas used.
    #[error("blob gas used mismatch: {0}")]
    BlobGasUsedDiff(GotExpected<u64>),

    /// Error for a transaction that violates consensus.
    #[error(transparent)]
    InvalidTransaction(InvalidTransactionError),

    /// 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} exceeds the max allowed increase ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
    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 indicating that the block gas limit is above the allowed maximum.
    ///
    /// This error occurs when the gas limit is more than the specified maximum gas limit.
    #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
    GasLimitInvalidBlockMaximum {
        /// block gas limit.
        block_gas_limit: u64,
    },

    /// Error when the child gas limit exceeds the maximum allowed decrease.
    #[error("child gas_limit {child_gas_limit} is below the max allowed decrease ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
    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,
    },
    /// Error when the block is too large.
    #[error("block is too large: {rlp_length} > {max_rlp_length}")]
    BlockTooLarge {
        /// The actual RLP length of the block.
        rlp_length: usize,
        /// The maximum allowed RLP length.
        max_rlp_length: usize,
    },
    /// EIP-7825: Transaction gas limit exceeds maximum allowed
    #[error(transparent)]
    TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
    /// EIP-7928: Error when an unexpected block access list cost is encountered.
    #[error(transparent)]
    BlockAccessListCostMoreThanGasLimit(Box<BlockAccessListGasError>),
    /// EIP-7928: Error when the block access list hash doesn't match the expected value.
    #[error("block access list hash mismatch: {0}")]
    BlockAccessListHashMismatch(GotExpectedBoxed<B256>),
    /// EIP-7928: Error when the block access list cannot be decoded or converted.
    #[error("invalid block access list: {0}")]
    BlockAccessListInvalid(String),
    /// Any additional consensus error, for example L2-specific errors.
    #[error(transparent)]
    Other(#[from] Arc<dyn Error + Send + Sync>),
}

impl ConsensusError {
    /// Returns a new [`ConsensusError::Other`] instance with the given error.
    pub fn other<E>(error: E) -> Self
    where
        E: Error + Send + Sync + 'static,
    {
        Self::Other(Arc::new(error))
    }

    /// Returns a new [`ConsensusError::Other`] instance with the given message.
    pub fn msg(msg: impl Display) -> Self {
        Self::other(MessageError(msg.to_string()))
    }

    /// Returns `true` if the error is a state root error.
    pub const fn is_state_root_error(&self) -> bool {
        matches!(self, Self::BodyStateRootDiff(_))
    }

    /// Returns the arbitrary error if it is [`ConsensusError::Other`].
    pub fn as_other(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
        match self {
            Self::Other(err) => Some(err.as_ref()),
            _ => None,
        }
    }

    /// Returns a reference to the [`ConsensusError::Other`] value if it is of that type.
    /// Returns `None` otherwise.
    pub fn downcast_other_ref<T: Error + 'static>(&self) -> Option<&T> {
        let other = self.as_other()?;
        other.downcast_ref()
    }

    /// Returns `true` if this type is a [`ConsensusError::Other`] of that error type.
    pub fn is_other<T: Error + 'static>(&self) -> bool {
        self.as_other().map(|err| err.is::<T>()).unwrap_or(false)
    }
}

impl From<InvalidTransactionError> for ConsensusError {
    fn from(value: InvalidTransactionError) -> Self {
        Self::InvalidTransaction(value)
    }
}

impl From<TxGasLimitTooHighErr> for ConsensusError {
    fn from(value: TxGasLimitTooHighErr) -> Self {
        Self::TransactionGasLimitTooHigh(Box::new(value))
    }
}

impl From<BlockAccessListGasError> for ConsensusError {
    fn from(value: BlockAccessListGasError) -> Self {
        Self::BlockAccessListCostMoreThanGasLimit(Box::new(value))
    }
}

/// `HeaderConsensusError` combines a `ConsensusError` with the `SealedHeader` it relates to.
#[derive(thiserror::Error, Debug)]
#[error("Consensus error: {0}, Invalid header: {1:?}")]
pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);

/// EIP-7825: Transaction gas limit exceeds maximum allowed
#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
#[error("transaction gas limit ({gas_limit}) is greater than the cap ({max_allowed})")]
pub struct TxGasLimitTooHighErr {
    /// Hash of the transaction that violates the rule
    pub tx_hash: B256,
    /// The gas limit of the transaction
    pub gas_limit: u64,
    /// The maximum allowed gas limit
    pub max_allowed: u64,
}

#[derive(Debug, thiserror::Error)]
#[error("{0}")]
struct MessageError(String);

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(thiserror::Error, Debug)]
    #[error("Custom L2 consensus error")]
    struct CustomL2Error;

    #[test]
    fn test_other_error_conversion() {
        let consensus_err = ConsensusError::other(CustomL2Error);
        assert!(matches!(consensus_err, ConsensusError::Other(_)));
    }

    #[test]
    fn test_other_error_display() {
        let consensus_err = ConsensusError::other(CustomL2Error);
        let error_message = format!("{}", consensus_err);
        assert_eq!(error_message, "Custom L2 consensus error");
    }

    #[test]
    fn test_other_error_downcast() {
        let consensus_err = ConsensusError::other(CustomL2Error);

        assert!(consensus_err.is_other::<CustomL2Error>());
        assert!(consensus_err.downcast_other_ref::<CustomL2Error>().is_some());
    }

    #[test]
    fn test_other_msg() {
        let consensus_err = ConsensusError::msg("consensus message");

        assert_eq!(consensus_err.to_string(), "consensus message");
        assert!(consensus_err.downcast_other_ref::<MessageError>().is_some());
    }
}
