1#![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
19pub type ReceiptRootBloom = (B256, Bloom);
24
25pub 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
38pub mod noop;
40
41#[cfg(any(test, feature = "test-utils"))]
42pub mod test_utils;
44
45#[auto_impl::auto_impl(&, Arc)]
48pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
49 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#[auto_impl::auto_impl(&, Arc)]
68pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
69 fn validate_body_against_header(
71 &self,
72 body: &B::Body,
73 header: &SealedHeader<B::Header>,
74 ) -> Result<(), ConsensusError>;
75
76 fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError>;
87
88 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#[auto_impl::auto_impl(&, Arc)]
107pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
108 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
112
113 fn validate_header_against_parent(
124 &self,
125 header: &SealedHeader<H>,
126 parent: &SealedHeader<H>,
127 ) -> Result<(), ConsensusError>;
128
129 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#[derive(Debug, Clone, thiserror::Error)]
159pub enum ConsensusError {
160 #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
162 HeaderGasUsedExceedsGasLimit {
163 gas_used: u64,
165 gas_limit: u64,
167 },
168 #[error(
170 "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
171 )]
172 HeaderGasLimitExceedsMax {
173 gas_limit: u64,
175 },
176
177 #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
179 BlockGasUsed {
180 gas: GotExpected<u64>,
182 gas_spent_by_tx: Vec<(u64, u64)>,
184 },
185
186 #[error("mismatched block ommer hash: {0}")]
188 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
189
190 #[error("mismatched block state root: {0}")]
192 BodyStateRootDiff(GotExpectedBoxed<B256>),
193
194 #[error("mismatched block transaction root: {0}")]
197 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
198
199 #[error("receipt root mismatch: {0}")]
201 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
202
203 #[error("header bloom filter mismatch: {0}")]
205 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
206
207 #[error("mismatched block withdrawals root: {0}")]
210 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
211
212 #[error("mismatched block requests hash: {0}")]
215 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
216
217 #[error("block with [hash={hash}, number={number}] is already known")]
219 BlockKnown {
220 hash: BlockHash,
222 number: BlockNumber,
224 },
225
226 #[error("block parent [hash={hash}] is not known")]
228 ParentUnknown {
229 hash: BlockHash,
231 },
232
233 #[error(
235 "block number {block_number} does not match parent block number {parent_block_number}"
236 )]
237 ParentBlockNumberMismatch {
238 parent_block_number: BlockNumber,
240 block_number: BlockNumber,
242 },
243
244 #[error("mismatched parent hash: {0}")]
246 ParentHashMismatch(GotExpectedBoxed<B256>),
247
248 #[error(
250 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
251 )]
252 TimestampIsInFuture {
253 timestamp: u64,
255 present_timestamp: u64,
257 },
258
259 #[error("base fee missing")]
261 BaseFeeMissing,
262
263 #[error("transaction signer recovery error")]
265 TransactionSignerRecoveryError,
266
267 #[error("extra data {len} exceeds max length")]
269 ExtraDataExceedsMax {
270 len: usize,
272 },
273
274 #[error("difficulty after merge is not zero")]
276 TheMergeDifficultyIsNotZero,
277
278 #[error("nonce after merge is not zero")]
280 TheMergeNonceIsNotZero,
281
282 #[error("ommer root after merge is not empty")]
284 TheMergeOmmerRootIsNotEmpty,
285
286 #[error("missing withdrawals root")]
288 WithdrawalsRootMissing,
289
290 #[error("missing requests hash")]
292 RequestsHashMissing,
293
294 #[error("unexpected withdrawals root")]
296 WithdrawalsRootUnexpected,
297
298 #[error("unexpected requests hash")]
300 RequestsHashUnexpected,
301
302 #[error("missing withdrawals")]
304 BodyWithdrawalsMissing,
305
306 #[error("missing requests")]
308 BodyRequestsMissing,
309
310 #[error("missing blob gas used")]
312 BlobGasUsedMissing,
313
314 #[error("unexpected blob gas used")]
316 BlobGasUsedUnexpected,
317
318 #[error("missing excess blob gas")]
320 ExcessBlobGasMissing,
321
322 #[error("unexpected excess blob gas")]
324 ExcessBlobGasUnexpected,
325
326 #[error("missing parent beacon block root")]
328 ParentBeaconBlockRootMissing,
329
330 #[error("unexpected parent beacon block root")]
332 ParentBeaconBlockRootUnexpected,
333
334 #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
336 BlobGasUsedExceedsMaxBlobGasPerBlock {
337 blob_gas_used: u64,
339 max_blob_gas_per_block: u64,
341 },
342
343 #[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 blob_gas_used: u64,
350 blob_gas_per_blob: u64,
352 },
353
354 #[error("blob gas used mismatch: {0}")]
356 BlobGasUsedDiff(GotExpected<u64>),
357
358 #[error(transparent)]
360 InvalidTransaction(InvalidTransactionError),
361
362 #[error("block base fee mismatch: {0}")]
364 BaseFeeDiff(GotExpected<u64>),
365
366 #[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 diff: GotExpected<u64>,
375 parent_excess_blob_gas: u64,
377 parent_blob_gas_used: u64,
379 },
380
381 #[error("child gas_limit {child_gas_limit} exceeds the max allowed increase ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
383 GasLimitInvalidIncrease {
384 parent_gas_limit: u64,
386 child_gas_limit: u64,
388 },
389
390 #[error(
394 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
395 )]
396 GasLimitInvalidMinimum {
397 child_gas_limit: u64,
399 },
400
401 #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
405 GasLimitInvalidBlockMaximum {
406 block_gas_limit: u64,
408 },
409
410 #[error("child gas_limit {child_gas_limit} is below the max allowed decrease ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
412 GasLimitInvalidDecrease {
413 parent_gas_limit: u64,
415 child_gas_limit: u64,
417 },
418
419 #[error(
421 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
422 )]
423 TimestampIsInPast {
424 parent_timestamp: u64,
426 timestamp: u64,
428 },
429 #[error("block is too large: {rlp_length} > {max_rlp_length}")]
431 BlockTooLarge {
432 rlp_length: usize,
434 max_rlp_length: usize,
436 },
437 #[error(transparent)]
439 TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
440 #[error("{0}")]
442 Other(String),
443 #[error(transparent)]
445 Custom(#[from] Arc<dyn Error + Send + Sync>),
446}
447
448impl ConsensusError {
449 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#[derive(thiserror::Error, Debug)]
469#[error("Consensus error: {0}, Invalid header: {1:?}")]
470pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);
471
472#[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 pub tx_hash: B256,
478 pub gas_limit: u64,
480 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 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 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 let error_message = format!("{}", consensus_err);
511 assert_eq!(error_message, "Custom L2 consensus error");
512 }
513}