Skip to main content

reth_consensus/
lib.rs

1//! Consensus protocol functions
2//!
3//! # Trait hierarchy
4//!
5//! Consensus validation is split across three traits, each adding a layer:
6//!
7//! - [`HeaderValidator`] — validates a header in isolation and against its parent. Used early in
8//!   the validation pipeline before block execution.
9//!
10//! - [`Consensus`] — extends `HeaderValidator` with block body validation. Checks that the body
11//!   matches the header (tx root, ommer hash, withdrawals) and runs pre-execution checks. Used
12//!   before a block is executed.
13//!
14//! - [`FullConsensus`] — extends `Consensus` with post-execution validation. Checks execution
15//!   results against the header (gas used, receipt root, logs bloom). Used after block execution to
16//!   verify the outcome.
17//!
18//! In the engine, these are applied in order during payload validation (`engine_newPayload`).
19//! Payload attribute validation for block building (`engine_forkchoiceUpdated`) is handled
20//! separately at the engine API layer and does not use these traits.
21
22#![doc(
23    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
24    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
25    issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
26)]
27#![cfg_attr(not(test), warn(unused_crate_dependencies))]
28#![cfg_attr(docsrs, feature(doc_cfg))]
29#![cfg_attr(not(feature = "std"), no_std)]
30
31extern crate alloc;
32
33use alloc::{boxed::Box, fmt::Debug, string::String, sync::Arc, vec::Vec};
34use alloy_consensus::Header;
35use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
36use core::error::Error;
37
38/// Pre-computed receipt root and logs bloom.
39///
40/// When provided to [`FullConsensus::validate_block_post_execution`], this allows skipping
41/// the receipt root computation and using the pre-computed values instead.
42pub type ReceiptRootBloom = (B256, Bloom);
43
44/// Pre-computed transaction root.
45///
46/// When provided to [`Consensus::validate_block_pre_execution_with_tx_root`], this allows
47/// skipping transaction trie reconstruction from the block body.
48pub type TransactionRoot = B256;
49use reth_execution_types::BlockExecutionResult;
50use reth_primitives_traits::{
51    constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
52    transaction::error::InvalidTransactionError,
53    Block, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock,
54    SealedHeader,
55};
56
57/// A consensus implementation that does nothing.
58pub mod noop;
59
60#[cfg(any(test, feature = "test-utils"))]
61/// test helpers for mocking consensus
62pub mod test_utils;
63
64/// [`Consensus`] implementation which knows full node primitives and is able to validation block's
65/// execution outcome.
66#[auto_impl::auto_impl(&, Arc)]
67pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
68    /// Validate a block considering world state, i.e. things that can not be checked before
69    /// execution.
70    ///
71    /// See the Yellow Paper sections 4.3.2 "Holistic Validity".
72    ///
73    /// If `receipt_root_bloom` is provided, the implementation should use the pre-computed
74    /// receipt root and logs bloom instead of computing them from the receipts.
75    ///
76    /// Note: validating blocks does not include other validations of the Consensus
77    fn validate_block_post_execution(
78        &self,
79        block: &RecoveredBlock<N::Block>,
80        result: &BlockExecutionResult<N::Receipt>,
81        receipt_root_bloom: Option<ReceiptRootBloom>,
82    ) -> Result<(), ConsensusError>;
83}
84
85/// Consensus is a protocol that chooses canonical chain.
86#[auto_impl::auto_impl(&, Arc)]
87pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
88    /// Ensures that body field values match the header.
89    fn validate_body_against_header(
90        &self,
91        body: &B::Body,
92        header: &SealedHeader<B::Header>,
93    ) -> Result<(), ConsensusError>;
94
95    /// Validate a block disregarding world state, i.e. things that can be checked before sender
96    /// recovery and execution.
97    ///
98    /// See the Yellow Paper sections 4.4.2 "Holistic Validity", 4.4.4 "Block Header Validity".
99    /// Note: Ommer Validation (previously section 11.1) has been deprecated since the Paris hard
100    /// fork transition to proof of stake.
101    ///
102    /// **This should not be called for the genesis block**.
103    ///
104    /// Note: validating blocks does not include other validations of the Consensus
105    fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError>;
106
107    /// Validate a block disregarding world state using an optional pre-computed transaction root.
108    ///
109    /// If `transaction_root` is provided, the implementation should use the pre-computed
110    /// transaction root instead of recomputing it from the block body. The value must have been
111    /// derived from `block.body().calculate_tx_root()`.
112    ///
113    /// By default this falls back to [`Self::validate_block_pre_execution`].
114    fn validate_block_pre_execution_with_tx_root(
115        &self,
116        block: &SealedBlock<B>,
117        transaction_root: Option<TransactionRoot>,
118    ) -> Result<(), ConsensusError> {
119        let _ = transaction_root;
120        self.validate_block_pre_execution(block)
121    }
122}
123
124/// `HeaderValidator` is a protocol that validates headers and their relationships.
125#[auto_impl::auto_impl(&, Arc)]
126pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
127    /// Validate if header is correct and follows consensus specification.
128    ///
129    /// This is called on standalone header to check if all hashes are correct.
130    fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
131
132    /// Validate that the header information regarding parent are correct.
133    /// This checks the block number, timestamp, basefee and gas limit increment.
134    ///
135    /// This is called before properties that are not in the header itself (like total difficulty)
136    /// have been computed.
137    ///
138    /// **This should not be called for the genesis block**.
139    ///
140    /// Note: Validating header against its parent does not include other `HeaderValidator`
141    /// validations.
142    fn validate_header_against_parent(
143        &self,
144        header: &SealedHeader<H>,
145        parent: &SealedHeader<H>,
146    ) -> Result<(), ConsensusError>;
147
148    /// Validates the given headers
149    ///
150    /// This ensures that the first header is valid on its own and all subsequent headers are valid
151    /// on its own and valid against its parent.
152    ///
153    /// Note: this expects that the headers are in natural order (ascending block number)
154    fn validate_header_range(
155        &self,
156        headers: &[SealedHeader<H>],
157    ) -> Result<(), HeaderConsensusError<H>>
158    where
159        H: Clone,
160    {
161        if let Some((initial_header, remaining_headers)) = headers.split_first() {
162            self.validate_header(initial_header)
163                .map_err(|e| HeaderConsensusError(e, initial_header.clone()))?;
164            let mut parent = initial_header;
165            for child in remaining_headers {
166                self.validate_header(child).map_err(|e| HeaderConsensusError(e, child.clone()))?;
167                self.validate_header_against_parent(child, parent)
168                    .map_err(|e| HeaderConsensusError(e, child.clone()))?;
169                parent = child;
170            }
171        }
172        Ok(())
173    }
174}
175
176/// Consensus Errors
177#[derive(Debug, Clone, thiserror::Error)]
178pub enum ConsensusError {
179    /// Error when the gas used in the header exceeds the gas limit.
180    #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
181    HeaderGasUsedExceedsGasLimit {
182        /// The gas used in the block header.
183        gas_used: u64,
184        /// The gas limit in the block header.
185        gas_limit: u64,
186    },
187    /// Error when the gas limit is more than the maximum allowed.
188    #[error(
189        "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
190    )]
191    HeaderGasLimitExceedsMax {
192        /// The gas limit in the block header.
193        gas_limit: u64,
194    },
195
196    /// Error when block gas used doesn't match expected value
197    #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
198    BlockGasUsed {
199        /// The gas diff.
200        gas: GotExpected<u64>,
201        /// Gas spent by each transaction
202        gas_spent_by_tx: Vec<(u64, u64)>,
203    },
204
205    /// Error when the hash of block ommer is different from the expected hash.
206    #[error("mismatched block ommer hash: {0}")]
207    BodyOmmersHashDiff(GotExpectedBoxed<B256>),
208
209    /// Error when the state root in the block is different from the expected state root.
210    #[error("mismatched block state root: {0}")]
211    BodyStateRootDiff(GotExpectedBoxed<B256>),
212
213    /// Error when the transaction root in the block is different from the expected transaction
214    /// root.
215    #[error("mismatched block transaction root: {0}")]
216    BodyTransactionRootDiff(GotExpectedBoxed<B256>),
217
218    /// Error when the receipt root in the block is different from the expected receipt root.
219    #[error("receipt root mismatch: {0}")]
220    BodyReceiptRootDiff(GotExpectedBoxed<B256>),
221
222    /// Error when header bloom filter is different from the expected bloom filter.
223    #[error("header bloom filter mismatch: {0}")]
224    BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
225
226    /// Error when the withdrawals root in the block is different from the expected withdrawals
227    /// root.
228    #[error("mismatched block withdrawals root: {0}")]
229    BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
230
231    /// Error when the requests hash in the block is different from the expected requests
232    /// hash.
233    #[error("mismatched block requests hash: {0}")]
234    BodyRequestsHashDiff(GotExpectedBoxed<B256>),
235
236    /// Error when a block with a specific hash and number is already known.
237    #[error("block with [hash={hash}, number={number}] is already known")]
238    BlockKnown {
239        /// The hash of the known block.
240        hash: BlockHash,
241        /// The block number of the known block.
242        number: BlockNumber,
243    },
244
245    /// Error when the parent hash of a block is not known.
246    #[error("block parent [hash={hash}] is not known")]
247    ParentUnknown {
248        /// The hash of the unknown parent block.
249        hash: BlockHash,
250    },
251
252    /// Error when the block number does not match the parent block number.
253    #[error(
254        "block number {block_number} does not match parent block number {parent_block_number}"
255    )]
256    ParentBlockNumberMismatch {
257        /// The parent block number.
258        parent_block_number: BlockNumber,
259        /// The block number.
260        block_number: BlockNumber,
261    },
262
263    /// Error when the parent hash does not match the expected parent hash.
264    #[error("mismatched parent hash: {0}")]
265    ParentHashMismatch(GotExpectedBoxed<B256>),
266
267    /// Error when the block timestamp is in the future compared to our clock time.
268    #[error(
269        "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
270    )]
271    TimestampIsInFuture {
272        /// The block's timestamp.
273        timestamp: u64,
274        /// The current timestamp.
275        present_timestamp: u64,
276    },
277
278    /// Error when the base fee is missing.
279    #[error("base fee missing")]
280    BaseFeeMissing,
281
282    /// Error when there is a transaction signer recovery error.
283    #[error("transaction signer recovery error")]
284    TransactionSignerRecoveryError,
285
286    /// Error when the extra data length exceeds the maximum allowed.
287    #[error("extra data {len} exceeds max length")]
288    ExtraDataExceedsMax {
289        /// The length of the extra data.
290        len: usize,
291    },
292
293    /// Error when the difficulty after a merge is not zero.
294    #[error("difficulty after merge is not zero")]
295    TheMergeDifficultyIsNotZero,
296
297    /// Error when the nonce after a merge is not zero.
298    #[error("nonce after merge is not zero")]
299    TheMergeNonceIsNotZero,
300
301    /// Error when the ommer root after a merge is not empty.
302    #[error("ommer root after merge is not empty")]
303    TheMergeOmmerRootIsNotEmpty,
304
305    /// Error when the withdrawals root is missing.
306    #[error("missing withdrawals root")]
307    WithdrawalsRootMissing,
308
309    /// Error when the requests hash is missing.
310    #[error("missing requests hash")]
311    RequestsHashMissing,
312
313    /// Error when an unexpected withdrawals root is encountered.
314    #[error("unexpected withdrawals root")]
315    WithdrawalsRootUnexpected,
316
317    /// Error when an unexpected requests hash is encountered.
318    #[error("unexpected requests hash")]
319    RequestsHashUnexpected,
320
321    /// Error when withdrawals are missing.
322    #[error("missing withdrawals")]
323    BodyWithdrawalsMissing,
324
325    /// Error when requests are missing.
326    #[error("missing requests")]
327    BodyRequestsMissing,
328
329    /// Error when blob gas used is missing.
330    #[error("missing blob gas used")]
331    BlobGasUsedMissing,
332
333    /// Error when unexpected blob gas used is encountered.
334    #[error("unexpected blob gas used")]
335    BlobGasUsedUnexpected,
336
337    /// Error when excess blob gas is missing.
338    #[error("missing excess blob gas")]
339    ExcessBlobGasMissing,
340
341    /// Error when unexpected excess blob gas is encountered.
342    #[error("unexpected excess blob gas")]
343    ExcessBlobGasUnexpected,
344
345    /// Error when the parent beacon block root is missing.
346    #[error("missing parent beacon block root")]
347    ParentBeaconBlockRootMissing,
348
349    /// Error when an unexpected parent beacon block root is encountered.
350    #[error("unexpected parent beacon block root")]
351    ParentBeaconBlockRootUnexpected,
352
353    /// Error when blob gas used exceeds the maximum allowed.
354    #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
355    BlobGasUsedExceedsMaxBlobGasPerBlock {
356        /// The actual blob gas used.
357        blob_gas_used: u64,
358        /// The maximum allowed blob gas per block.
359        max_blob_gas_per_block: u64,
360    },
361
362    /// Error when blob gas used is not a multiple of blob gas per blob.
363    #[error(
364        "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
365    )]
366    BlobGasUsedNotMultipleOfBlobGasPerBlob {
367        /// The actual blob gas used.
368        blob_gas_used: u64,
369        /// The blob gas per blob.
370        blob_gas_per_blob: u64,
371    },
372
373    /// Error when the blob gas used in the header does not match the expected blob gas used.
374    #[error("blob gas used mismatch: {0}")]
375    BlobGasUsedDiff(GotExpected<u64>),
376
377    /// Error for a transaction that violates consensus.
378    #[error(transparent)]
379    InvalidTransaction(InvalidTransactionError),
380
381    /// Error when the block's base fee is different from the expected base fee.
382    #[error("block base fee mismatch: {0}")]
383    BaseFeeDiff(GotExpected<u64>),
384
385    /// Error when there is an invalid excess blob gas.
386    #[error(
387        "invalid excess blob gas: {diff}; \
388            parent excess blob gas: {parent_excess_blob_gas}, \
389            parent blob gas used: {parent_blob_gas_used}"
390    )]
391    ExcessBlobGasDiff {
392        /// The excess blob gas diff.
393        diff: GotExpected<u64>,
394        /// The parent excess blob gas.
395        parent_excess_blob_gas: u64,
396        /// The parent blob gas used.
397        parent_blob_gas_used: u64,
398    },
399
400    /// Error when the child gas limit exceeds the maximum allowed increase.
401    #[error("child gas_limit {child_gas_limit} exceeds the max allowed increase ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
402    GasLimitInvalidIncrease {
403        /// The parent gas limit.
404        parent_gas_limit: u64,
405        /// The child gas limit.
406        child_gas_limit: u64,
407    },
408
409    /// Error indicating that the child gas limit is below the minimum allowed limit.
410    ///
411    /// This error occurs when the child gas limit is less than the specified minimum gas limit.
412    #[error(
413        "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
414    )]
415    GasLimitInvalidMinimum {
416        /// The child gas limit.
417        child_gas_limit: u64,
418    },
419
420    /// Error indicating that the block gas limit is above the allowed maximum.
421    ///
422    /// This error occurs when the gas limit is more than the specified maximum gas limit.
423    #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
424    GasLimitInvalidBlockMaximum {
425        /// block gas limit.
426        block_gas_limit: u64,
427    },
428
429    /// Error when the child gas limit exceeds the maximum allowed decrease.
430    #[error("child gas_limit {child_gas_limit} is below the max allowed decrease ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
431    GasLimitInvalidDecrease {
432        /// The parent gas limit.
433        parent_gas_limit: u64,
434        /// The child gas limit.
435        child_gas_limit: u64,
436    },
437
438    /// Error when the block timestamp is in the past compared to the parent timestamp.
439    #[error(
440        "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
441    )]
442    TimestampIsInPast {
443        /// The parent block's timestamp.
444        parent_timestamp: u64,
445        /// The block's timestamp.
446        timestamp: u64,
447    },
448    /// Error when the block is too large.
449    #[error("block is too large: {rlp_length} > {max_rlp_length}")]
450    BlockTooLarge {
451        /// The actual RLP length of the block.
452        rlp_length: usize,
453        /// The maximum allowed RLP length.
454        max_rlp_length: usize,
455    },
456    /// EIP-7825: Transaction gas limit exceeds maximum allowed
457    #[error(transparent)]
458    TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
459    /// Other, likely an injected L2 error.
460    #[error("{0}")]
461    Other(String),
462    /// Other unspecified error.
463    #[error(transparent)]
464    Custom(#[from] Arc<dyn Error + Send + Sync>),
465}
466
467impl ConsensusError {
468    /// Returns `true` if the error is a state root error.
469    pub const fn is_state_root_error(&self) -> bool {
470        matches!(self, Self::BodyStateRootDiff(_))
471    }
472}
473
474impl From<InvalidTransactionError> for ConsensusError {
475    fn from(value: InvalidTransactionError) -> Self {
476        Self::InvalidTransaction(value)
477    }
478}
479
480impl From<TxGasLimitTooHighErr> for ConsensusError {
481    fn from(value: TxGasLimitTooHighErr) -> Self {
482        Self::TransactionGasLimitTooHigh(Box::new(value))
483    }
484}
485
486/// `HeaderConsensusError` combines a `ConsensusError` with the `SealedHeader` it relates to.
487#[derive(thiserror::Error, Debug)]
488#[error("Consensus error: {0}, Invalid header: {1:?}")]
489pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);
490
491/// EIP-7825: Transaction gas limit exceeds maximum allowed
492#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
493#[error("transaction gas limit ({gas_limit}) is greater than the cap ({max_allowed})")]
494pub struct TxGasLimitTooHighErr {
495    /// Hash of the transaction that violates the rule
496    pub tx_hash: B256,
497    /// The gas limit of the transaction
498    pub gas_limit: u64,
499    /// The maximum allowed gas limit
500    pub max_allowed: u64,
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    #[derive(thiserror::Error, Debug)]
508    #[error("Custom L2 consensus error")]
509    struct CustomL2Error;
510
511    #[test]
512    fn test_custom_error_conversion() {
513        // Test conversion from custom error to ConsensusError
514        let custom_err = CustomL2Error;
515        let arc_err: Arc<dyn Error + Send + Sync> = Arc::new(custom_err);
516        let consensus_err: ConsensusError = arc_err.into();
517
518        // Verify it's the Custom variant
519        assert!(matches!(consensus_err, ConsensusError::Custom(_)));
520    }
521
522    #[test]
523    fn test_custom_error_display() {
524        let custom_err = CustomL2Error;
525        let arc_err: Arc<dyn Error + Send + Sync> = Arc::new(custom_err);
526        let consensus_err: ConsensusError = arc_err.into();
527
528        // Verify the error message is preserved through transparent attribute
529        let error_message = format!("{}", consensus_err);
530        assert_eq!(error_message, "Custom L2 consensus error");
531    }
532}