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);
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
32pub mod noop;
34
35#[cfg(any(test, feature = "test-utils"))]
36pub mod test_utils;
38
39#[auto_impl::auto_impl(&, Arc)]
42pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
43 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#[auto_impl::auto_impl(&, Arc)]
62pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
63 fn validate_body_against_header(
65 &self,
66 body: &B::Body,
67 header: &SealedHeader<B::Header>,
68 ) -> Result<(), ConsensusError>;
69
70 fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError>;
81}
82
83#[auto_impl::auto_impl(&, Arc)]
85pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
86 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
90
91 fn validate_header_against_parent(
102 &self,
103 header: &SealedHeader<H>,
104 parent: &SealedHeader<H>,
105 ) -> Result<(), ConsensusError>;
106
107 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#[derive(Debug, Clone, thiserror::Error)]
137pub enum ConsensusError {
138 #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
140 HeaderGasUsedExceedsGasLimit {
141 gas_used: u64,
143 gas_limit: u64,
145 },
146 #[error(
148 "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
149 )]
150 HeaderGasLimitExceedsMax {
151 gas_limit: u64,
153 },
154
155 #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
157 BlockGasUsed {
158 gas: GotExpected<u64>,
160 gas_spent_by_tx: Vec<(u64, u64)>,
162 },
163
164 #[error("mismatched block ommer hash: {0}")]
166 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
167
168 #[error("mismatched block state root: {0}")]
170 BodyStateRootDiff(GotExpectedBoxed<B256>),
171
172 #[error("mismatched block transaction root: {0}")]
175 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
176
177 #[error("receipt root mismatch: {0}")]
179 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
180
181 #[error("header bloom filter mismatch: {0}")]
183 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
184
185 #[error("mismatched block withdrawals root: {0}")]
188 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
189
190 #[error("mismatched block requests hash: {0}")]
193 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
194
195 #[error("block with [hash={hash}, number={number}] is already known")]
197 BlockKnown {
198 hash: BlockHash,
200 number: BlockNumber,
202 },
203
204 #[error("block parent [hash={hash}] is not known")]
206 ParentUnknown {
207 hash: BlockHash,
209 },
210
211 #[error(
213 "block number {block_number} does not match parent block number {parent_block_number}"
214 )]
215 ParentBlockNumberMismatch {
216 parent_block_number: BlockNumber,
218 block_number: BlockNumber,
220 },
221
222 #[error("mismatched parent hash: {0}")]
224 ParentHashMismatch(GotExpectedBoxed<B256>),
225
226 #[error(
228 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
229 )]
230 TimestampIsInFuture {
231 timestamp: u64,
233 present_timestamp: u64,
235 },
236
237 #[error("base fee missing")]
239 BaseFeeMissing,
240
241 #[error("transaction signer recovery error")]
243 TransactionSignerRecoveryError,
244
245 #[error("extra data {len} exceeds max length")]
247 ExtraDataExceedsMax {
248 len: usize,
250 },
251
252 #[error("difficulty after merge is not zero")]
254 TheMergeDifficultyIsNotZero,
255
256 #[error("nonce after merge is not zero")]
258 TheMergeNonceIsNotZero,
259
260 #[error("ommer root after merge is not empty")]
262 TheMergeOmmerRootIsNotEmpty,
263
264 #[error("missing withdrawals root")]
266 WithdrawalsRootMissing,
267
268 #[error("missing requests hash")]
270 RequestsHashMissing,
271
272 #[error("unexpected withdrawals root")]
274 WithdrawalsRootUnexpected,
275
276 #[error("unexpected requests hash")]
278 RequestsHashUnexpected,
279
280 #[error("missing withdrawals")]
282 BodyWithdrawalsMissing,
283
284 #[error("missing requests")]
286 BodyRequestsMissing,
287
288 #[error("missing blob gas used")]
290 BlobGasUsedMissing,
291
292 #[error("unexpected blob gas used")]
294 BlobGasUsedUnexpected,
295
296 #[error("missing excess blob gas")]
298 ExcessBlobGasMissing,
299
300 #[error("unexpected excess blob gas")]
302 ExcessBlobGasUnexpected,
303
304 #[error("missing parent beacon block root")]
306 ParentBeaconBlockRootMissing,
307
308 #[error("unexpected parent beacon block root")]
310 ParentBeaconBlockRootUnexpected,
311
312 #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
314 BlobGasUsedExceedsMaxBlobGasPerBlock {
315 blob_gas_used: u64,
317 max_blob_gas_per_block: u64,
319 },
320
321 #[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 blob_gas_used: u64,
328 blob_gas_per_blob: u64,
330 },
331
332 #[error("blob gas used mismatch: {0}")]
334 BlobGasUsedDiff(GotExpected<u64>),
335
336 #[error(transparent)]
338 InvalidTransaction(InvalidTransactionError),
339
340 #[error("block base fee mismatch: {0}")]
342 BaseFeeDiff(GotExpected<u64>),
343
344 #[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 diff: GotExpected<u64>,
353 parent_excess_blob_gas: u64,
355 parent_blob_gas_used: u64,
357 },
358
359 #[error("child gas_limit {child_gas_limit} exceeds the max allowed increase ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
361 GasLimitInvalidIncrease {
362 parent_gas_limit: u64,
364 child_gas_limit: u64,
366 },
367
368 #[error(
372 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
373 )]
374 GasLimitInvalidMinimum {
375 child_gas_limit: u64,
377 },
378
379 #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
383 GasLimitInvalidBlockMaximum {
384 block_gas_limit: u64,
386 },
387
388 #[error("child gas_limit {child_gas_limit} is below the max allowed decrease ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
390 GasLimitInvalidDecrease {
391 parent_gas_limit: u64,
393 child_gas_limit: u64,
395 },
396
397 #[error(
399 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
400 )]
401 TimestampIsInPast {
402 parent_timestamp: u64,
404 timestamp: u64,
406 },
407 #[error("block is too large: {rlp_length} > {max_rlp_length}")]
409 BlockTooLarge {
410 rlp_length: usize,
412 max_rlp_length: usize,
414 },
415 #[error(transparent)]
417 TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
418 #[error("{0}")]
420 Other(String),
421 #[error(transparent)]
423 Custom(#[from] Arc<dyn Error + Send + Sync>),
424}
425
426impl ConsensusError {
427 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#[derive(thiserror::Error, Debug)]
447#[error("Consensus error: {0}, Invalid header: {1:?}")]
448pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);
449
450#[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 pub tx_hash: B256,
456 pub gas_limit: u64,
458 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 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 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 let error_message = format!("{}", consensus_err);
489 assert_eq!(error_message, "Custom L2 consensus error");
490 }
491}