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