Skip to main content

reth_consensus/
lib.rs

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