1#![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::{
34 boxed::Box,
35 fmt::Debug,
36 string::{String, ToString},
37 sync::Arc,
38 vec::Vec,
39};
40use alloy_consensus::Header;
41use alloy_eip7928::BlockAccessListGasError;
42use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
43use core::{error::Error, fmt::Display};
44
45pub type ReceiptRootBloom = (B256, Bloom);
50
51pub type TransactionRoot = B256;
56use reth_execution_types::BlockExecutionResult;
57use reth_primitives_traits::{
58 constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
59 transaction::error::InvalidTransactionError,
60 Block, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock,
61 SealedHeader,
62};
63
64pub mod noop;
66
67#[cfg(any(test, feature = "test-utils"))]
68pub mod test_utils;
70
71#[auto_impl::auto_impl(&, Arc)]
74pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
75 fn validate_block_post_execution(
85 &self,
86 block: &RecoveredBlock<N::Block>,
87 result: &BlockExecutionResult<N::Receipt>,
88 receipt_root_bloom: Option<ReceiptRootBloom>,
89 block_access_list_hash: Option<B256>,
90 ) -> Result<(), ConsensusError>;
91}
92
93#[auto_impl::auto_impl(&, Arc)]
95pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
96 fn validate_body_against_header(
98 &self,
99 body: &B::Body,
100 header: &SealedHeader<B::Header>,
101 ) -> Result<(), ConsensusError>;
102
103 fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError>;
114
115 fn is_transient_error(&self, _error: &ConsensusError) -> bool {
124 false
125 }
126
127 fn validate_block_pre_execution_with_tx_root(
135 &self,
136 block: &SealedBlock<B>,
137 transaction_root: Option<TransactionRoot>,
138 ) -> Result<(), ConsensusError> {
139 let _ = transaction_root;
140 self.validate_block_pre_execution(block)
141 }
142}
143
144#[auto_impl::auto_impl(&, Arc)]
146pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
147 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
151
152 fn validate_header_against_parent(
163 &self,
164 header: &SealedHeader<H>,
165 parent: &SealedHeader<H>,
166 ) -> Result<(), ConsensusError>;
167
168 fn validate_header_range(
175 &self,
176 headers: &[SealedHeader<H>],
177 ) -> Result<(), HeaderConsensusError<H>>
178 where
179 H: Clone,
180 {
181 if let Some((initial_header, remaining_headers)) = headers.split_first() {
182 self.validate_header(initial_header)
183 .map_err(|e| HeaderConsensusError(e, initial_header.clone()))?;
184 let mut parent = initial_header;
185 for child in remaining_headers {
186 self.validate_header(child).map_err(|e| HeaderConsensusError(e, child.clone()))?;
187 self.validate_header_against_parent(child, parent)
188 .map_err(|e| HeaderConsensusError(e, child.clone()))?;
189 parent = child;
190 }
191 }
192 Ok(())
193 }
194}
195
196#[derive(Debug, Clone, thiserror::Error)]
198pub enum ConsensusError {
199 #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
201 HeaderGasUsedExceedsGasLimit {
202 gas_used: u64,
204 gas_limit: u64,
206 },
207 #[error(
209 "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
210 )]
211 HeaderGasLimitExceedsMax {
212 gas_limit: u64,
214 },
215
216 #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
218 BlockGasUsed {
219 gas: GotExpected<u64>,
221 gas_spent_by_tx: Vec<(u64, u64)>,
223 },
224
225 #[error("mismatched block ommer hash: {0}")]
227 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
228
229 #[error("mismatched block state root: {0}")]
231 BodyStateRootDiff(GotExpectedBoxed<B256>),
232
233 #[error("mismatched block transaction root: {0}")]
236 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
237
238 #[error("receipt root mismatch: {0}")]
240 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
241
242 #[error("header bloom filter mismatch: {0}")]
244 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
245
246 #[error("mismatched block withdrawals root: {0}")]
249 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
250
251 #[error("mismatched block requests hash: {0}")]
254 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
255
256 #[error("block with [hash={hash}, number={number}] is already known")]
258 BlockKnown {
259 hash: BlockHash,
261 number: BlockNumber,
263 },
264
265 #[error("block parent [hash={hash}] is not known")]
267 ParentUnknown {
268 hash: BlockHash,
270 },
271
272 #[error(
274 "block number {block_number} does not match parent block number {parent_block_number}"
275 )]
276 ParentBlockNumberMismatch {
277 parent_block_number: BlockNumber,
279 block_number: BlockNumber,
281 },
282
283 #[error("mismatched parent hash: {0}")]
285 ParentHashMismatch(GotExpectedBoxed<B256>),
286
287 #[error(
289 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
290 )]
291 TimestampIsInFuture {
292 timestamp: u64,
294 present_timestamp: u64,
296 },
297
298 #[error("base fee missing")]
300 BaseFeeMissing,
301
302 #[error("transaction signer recovery error")]
304 TransactionSignerRecoveryError,
305
306 #[error("extra data {len} exceeds max length")]
308 ExtraDataExceedsMax {
309 len: usize,
311 },
312
313 #[error("difficulty after merge is not zero")]
315 TheMergeDifficultyIsNotZero,
316
317 #[error("nonce after merge is not zero")]
319 TheMergeNonceIsNotZero,
320
321 #[error("ommer root after merge is not empty")]
323 TheMergeOmmerRootIsNotEmpty,
324
325 #[error("missing withdrawals root")]
327 WithdrawalsRootMissing,
328
329 #[error("missing requests hash")]
331 RequestsHashMissing,
332
333 #[error("missing block access list hash")]
335 BlockAccessListHashMissing,
336
337 #[error("missing slot number")]
339 SlotNumberMissing,
340
341 #[error("unexpected withdrawals root")]
343 WithdrawalsRootUnexpected,
344
345 #[error("unexpected requests hash")]
347 RequestsHashUnexpected,
348
349 #[error("unexpected block access list hash")]
351 BlockAccessListHashUnexpected,
352
353 #[error("unexpected slot number")]
355 SlotNumberUnexpected,
356
357 #[error("missing withdrawals")]
359 BodyWithdrawalsMissing,
360
361 #[error("unexpected withdrawals")]
363 BodyWithdrawalsUnexpected,
364
365 #[error("missing requests")]
367 BodyRequestsMissing,
368
369 #[error("missing blob gas used")]
371 BlobGasUsedMissing,
372
373 #[error("unexpected blob gas used")]
375 BlobGasUsedUnexpected,
376
377 #[error("missing excess blob gas")]
379 ExcessBlobGasMissing,
380
381 #[error("unexpected excess blob gas")]
383 ExcessBlobGasUnexpected,
384
385 #[error("missing parent beacon block root")]
387 ParentBeaconBlockRootMissing,
388
389 #[error("unexpected parent beacon block root")]
391 ParentBeaconBlockRootUnexpected,
392
393 #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
395 BlobGasUsedExceedsMaxBlobGasPerBlock {
396 blob_gas_used: u64,
398 max_blob_gas_per_block: u64,
400 },
401
402 #[error(
404 "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
405 )]
406 BlobGasUsedNotMultipleOfBlobGasPerBlob {
407 blob_gas_used: u64,
409 blob_gas_per_blob: u64,
411 },
412
413 #[error("blob gas used mismatch: {0}")]
415 BlobGasUsedDiff(GotExpected<u64>),
416
417 #[error(transparent)]
419 InvalidTransaction(InvalidTransactionError),
420
421 #[error("block base fee mismatch: {0}")]
423 BaseFeeDiff(GotExpected<u64>),
424
425 #[error(
427 "invalid excess blob gas: {diff}; \
428 parent excess blob gas: {parent_excess_blob_gas}, \
429 parent blob gas used: {parent_blob_gas_used}"
430 )]
431 ExcessBlobGasDiff {
432 diff: GotExpected<u64>,
434 parent_excess_blob_gas: u64,
436 parent_blob_gas_used: u64,
438 },
439
440 #[error("child gas_limit {child_gas_limit} exceeds the max allowed increase ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
442 GasLimitInvalidIncrease {
443 parent_gas_limit: u64,
445 child_gas_limit: u64,
447 },
448
449 #[error(
453 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
454 )]
455 GasLimitInvalidMinimum {
456 child_gas_limit: u64,
458 },
459
460 #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
464 GasLimitInvalidBlockMaximum {
465 block_gas_limit: u64,
467 },
468
469 #[error("child gas_limit {child_gas_limit} is below the max allowed decrease ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
471 GasLimitInvalidDecrease {
472 parent_gas_limit: u64,
474 child_gas_limit: u64,
476 },
477
478 #[error(
480 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
481 )]
482 TimestampIsInPast {
483 parent_timestamp: u64,
485 timestamp: u64,
487 },
488 #[error("block is too large: {rlp_length} > {max_rlp_length}")]
490 BlockTooLarge {
491 rlp_length: usize,
493 max_rlp_length: usize,
495 },
496 #[error(transparent)]
498 TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
499 #[error(transparent)]
501 BlockAccessListCostMoreThanGasLimit(Box<BlockAccessListGasError>),
502 #[error("block access list hash mismatch: {0}")]
504 BlockAccessListHashMismatch(GotExpectedBoxed<B256>),
505 #[error(transparent)]
507 Other(#[from] Arc<dyn Error + Send + Sync>),
508}
509
510impl ConsensusError {
511 pub fn other<E>(error: E) -> Self
513 where
514 E: Error + Send + Sync + 'static,
515 {
516 Self::Other(Arc::new(error))
517 }
518
519 pub fn msg(msg: impl Display) -> Self {
521 Self::other(MessageError(msg.to_string()))
522 }
523
524 pub const fn is_state_root_error(&self) -> bool {
526 matches!(self, Self::BodyStateRootDiff(_))
527 }
528
529 pub fn as_other(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
531 match self {
532 Self::Other(err) => Some(err.as_ref()),
533 _ => None,
534 }
535 }
536
537 pub fn downcast_other_ref<T: Error + 'static>(&self) -> Option<&T> {
540 let other = self.as_other()?;
541 other.downcast_ref()
542 }
543
544 pub fn is_other<T: Error + 'static>(&self) -> bool {
546 self.as_other().is_some_and(|err| err.is::<T>())
547 }
548}
549
550impl From<InvalidTransactionError> for ConsensusError {
551 fn from(value: InvalidTransactionError) -> Self {
552 Self::InvalidTransaction(value)
553 }
554}
555
556impl From<TxGasLimitTooHighErr> for ConsensusError {
557 fn from(value: TxGasLimitTooHighErr) -> Self {
558 Self::TransactionGasLimitTooHigh(Box::new(value))
559 }
560}
561
562impl From<BlockAccessListGasError> for ConsensusError {
563 fn from(value: BlockAccessListGasError) -> Self {
564 Self::BlockAccessListCostMoreThanGasLimit(Box::new(value))
565 }
566}
567
568#[derive(thiserror::Error, Debug)]
570#[error("Consensus error: {0}, Invalid header: {1:?}")]
571pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);
572
573#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
575#[error("transaction gas limit ({gas_limit}) is greater than the cap ({max_allowed})")]
576pub struct TxGasLimitTooHighErr {
577 pub tx_hash: B256,
579 pub gas_limit: u64,
581 pub max_allowed: u64,
583}
584
585#[derive(Debug, thiserror::Error)]
586#[error("{0}")]
587struct MessageError(String);
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 #[derive(thiserror::Error, Debug)]
594 #[error("Custom L2 consensus error")]
595 struct CustomL2Error;
596
597 #[test]
598 fn test_other_error_conversion() {
599 let consensus_err = ConsensusError::other(CustomL2Error);
600 assert!(matches!(consensus_err, ConsensusError::Other(_)));
601 }
602
603 #[test]
604 fn test_other_error_display() {
605 let consensus_err = ConsensusError::other(CustomL2Error);
606 let error_message = format!("{}", consensus_err);
607 assert_eq!(error_message, "Custom L2 consensus error");
608 }
609
610 #[test]
611 fn test_other_error_downcast() {
612 let consensus_err = ConsensusError::other(CustomL2Error);
613
614 assert!(consensus_err.is_other::<CustomL2Error>());
615 assert!(consensus_err.downcast_other_ref::<CustomL2Error>().is_some());
616 }
617
618 #[test]
619 fn test_other_msg() {
620 let consensus_err = ConsensusError::msg("consensus message");
621
622 assert_eq!(consensus_err.to_string(), "consensus message");
623 assert!(consensus_err.downcast_other_ref::<MessageError>().is_some());
624 }
625}