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, doc_auto_cfg))]
10#![cfg_attr(not(feature = "std"), no_std)]
11
12extern crate alloc;
13
14use alloc::{fmt::Debug, string::String, sync::Arc, vec::Vec};
15use alloy_consensus::Header;
16use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256, U256};
17use reth_execution_types::BlockExecutionResult;
18use reth_primitives_traits::{
19 constants::{MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
20 transaction::error::InvalidTransactionError,
21 Block, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock,
22 SealedHeader,
23};
24
25pub mod noop;
27
28#[cfg(any(test, feature = "test-utils"))]
29pub mod test_utils;
31
32#[auto_impl::auto_impl(&, Arc)]
35pub trait FullConsensus<N: NodePrimitives>: AsConsensus<N::Block> {
36 fn validate_block_post_execution(
43 &self,
44 block: &RecoveredBlock<N::Block>,
45 result: &BlockExecutionResult<N::Receipt>,
46 ) -> Result<(), ConsensusError>;
47}
48
49#[auto_impl::auto_impl(&, Arc)]
51pub trait Consensus<B: Block>: AsHeaderValidator<B::Header> {
52 type Error;
54
55 fn validate_body_against_header(
57 &self,
58 body: &B::Body,
59 header: &SealedHeader<B::Header>,
60 ) -> Result<(), Self::Error>;
61
62 fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), Self::Error>;
72}
73
74#[auto_impl::auto_impl(&, Arc)]
76pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
77 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
81
82 fn validate_header_against_parent(
93 &self,
94 header: &SealedHeader<H>,
95 parent: &SealedHeader<H>,
96 ) -> Result<(), ConsensusError>;
97
98 fn validate_header_range(
105 &self,
106 headers: &[SealedHeader<H>],
107 ) -> Result<(), HeaderConsensusError<H>>
108 where
109 H: Clone,
110 {
111 if let Some((initial_header, remaining_headers)) = headers.split_first() {
112 self.validate_header(initial_header)
113 .map_err(|e| HeaderConsensusError(e, initial_header.clone()))?;
114 let mut parent = initial_header;
115 for child in remaining_headers {
116 self.validate_header(child).map_err(|e| HeaderConsensusError(e, child.clone()))?;
117 self.validate_header_against_parent(child, parent)
118 .map_err(|e| HeaderConsensusError(e, child.clone()))?;
119 parent = child;
120 }
121 }
122 Ok(())
123 }
124
125 fn validate_header_with_total_difficulty(
132 &self,
133 header: &H,
134 total_difficulty: U256,
135 ) -> Result<(), ConsensusError>;
136}
137
138pub trait AsHeaderValidator<H>: HeaderValidator<H> {
140 fn as_header_validator<'a>(self: Arc<Self>) -> Arc<dyn HeaderValidator<H> + 'a>
142 where
143 Self: 'a;
144}
145
146impl<T: HeaderValidator<H>, H> AsHeaderValidator<H> for T {
147 fn as_header_validator<'a>(self: Arc<Self>) -> Arc<dyn HeaderValidator<H> + 'a>
148 where
149 Self: 'a,
150 {
151 self
152 }
153}
154
155pub trait AsConsensus<B: Block>: Consensus<B> {
157 fn as_consensus<'a>(self: Arc<Self>) -> Arc<dyn Consensus<B, Error = Self::Error> + 'a>
159 where
160 Self: 'a;
161}
162
163impl<T: Consensus<B>, B: Block> AsConsensus<B> for T {
164 fn as_consensus<'a>(self: Arc<Self>) -> Arc<dyn Consensus<B, Error = Self::Error> + 'a>
165 where
166 Self: 'a,
167 {
168 self
169 }
170}
171
172#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
174pub enum ConsensusError {
175 #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
177 HeaderGasUsedExceedsGasLimit {
178 gas_used: u64,
180 gas_limit: u64,
182 },
183 #[error(
185 "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
186 )]
187 HeaderGasLimitExceedsMax {
188 gas_limit: u64,
190 },
191
192 #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
194 BlockGasUsed {
195 gas: GotExpected<u64>,
197 gas_spent_by_tx: Vec<(u64, u64)>,
199 },
200
201 #[error("mismatched block ommer hash: {0}")]
203 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
204
205 #[error("mismatched block state root: {0}")]
207 BodyStateRootDiff(GotExpectedBoxed<B256>),
208
209 #[error("mismatched block transaction root: {0}")]
212 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
213
214 #[error("receipt root mismatch: {0}")]
216 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
217
218 #[error("header bloom filter mismatch: {0}")]
220 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
221
222 #[error("mismatched block withdrawals root: {0}")]
225 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
226
227 #[error("mismatched block requests hash: {0}")]
230 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
231
232 #[error("block with [hash={hash}, number={number}] is already known")]
234 BlockKnown {
235 hash: BlockHash,
237 number: BlockNumber,
239 },
240
241 #[error("block parent [hash={hash}] is not known")]
243 ParentUnknown {
244 hash: BlockHash,
246 },
247
248 #[error(
250 "block number {block_number} does not match parent block number {parent_block_number}"
251 )]
252 ParentBlockNumberMismatch {
253 parent_block_number: BlockNumber,
255 block_number: BlockNumber,
257 },
258
259 #[error("mismatched parent hash: {0}")]
261 ParentHashMismatch(GotExpectedBoxed<B256>),
262
263 #[error(
265 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
266 )]
267 TimestampIsInFuture {
268 timestamp: u64,
270 present_timestamp: u64,
272 },
273
274 #[error("base fee missing")]
276 BaseFeeMissing,
277
278 #[error("transaction signer recovery error")]
280 TransactionSignerRecoveryError,
281
282 #[error("extra data {len} exceeds max length")]
284 ExtraDataExceedsMax {
285 len: usize,
287 },
288
289 #[error("difficulty after merge is not zero")]
291 TheMergeDifficultyIsNotZero,
292
293 #[error("nonce after merge is not zero")]
295 TheMergeNonceIsNotZero,
296
297 #[error("ommer root after merge is not empty")]
299 TheMergeOmmerRootIsNotEmpty,
300
301 #[error("missing withdrawals root")]
303 WithdrawalsRootMissing,
304
305 #[error("missing requests hash")]
307 RequestsHashMissing,
308
309 #[error("unexpected withdrawals root")]
311 WithdrawalsRootUnexpected,
312
313 #[error("unexpected requests hash")]
315 RequestsHashUnexpected,
316
317 #[error("missing withdrawals")]
319 BodyWithdrawalsMissing,
320
321 #[error("missing requests")]
323 BodyRequestsMissing,
324
325 #[error("missing blob gas used")]
327 BlobGasUsedMissing,
328
329 #[error("unexpected blob gas used")]
331 BlobGasUsedUnexpected,
332
333 #[error("missing excess blob gas")]
335 ExcessBlobGasMissing,
336
337 #[error("unexpected excess blob gas")]
339 ExcessBlobGasUnexpected,
340
341 #[error("missing parent beacon block root")]
343 ParentBeaconBlockRootMissing,
344
345 #[error("unexpected parent beacon block root")]
347 ParentBeaconBlockRootUnexpected,
348
349 #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
351 BlobGasUsedExceedsMaxBlobGasPerBlock {
352 blob_gas_used: u64,
354 max_blob_gas_per_block: u64,
356 },
357
358 #[error(
360 "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
361 )]
362 BlobGasUsedNotMultipleOfBlobGasPerBlob {
363 blob_gas_used: u64,
365 blob_gas_per_blob: u64,
367 },
368
369 #[error(
371 "excess blob gas {excess_blob_gas} is not a multiple of blob gas per blob {blob_gas_per_blob}"
372 )]
373 ExcessBlobGasNotMultipleOfBlobGasPerBlob {
374 excess_blob_gas: u64,
376 blob_gas_per_blob: u64,
378 },
379
380 #[error("blob gas used mismatch: {0}")]
382 BlobGasUsedDiff(GotExpected<u64>),
383
384 #[error(transparent)]
386 InvalidTransaction(InvalidTransactionError),
387
388 #[error("block base fee mismatch: {0}")]
390 BaseFeeDiff(GotExpected<u64>),
391
392 #[error(
394 "invalid excess blob gas: {diff}; \
395 parent excess blob gas: {parent_excess_blob_gas}, \
396 parent blob gas used: {parent_blob_gas_used}"
397 )]
398 ExcessBlobGasDiff {
399 diff: GotExpected<u64>,
401 parent_excess_blob_gas: u64,
403 parent_blob_gas_used: u64,
405 },
406
407 #[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")]
409 GasLimitInvalidIncrease {
410 parent_gas_limit: u64,
412 child_gas_limit: u64,
414 },
415
416 #[error(
420 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
421 )]
422 GasLimitInvalidMinimum {
423 child_gas_limit: u64,
425 },
426
427 #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
431 GasLimitInvalidBlockMaximum {
432 block_gas_limit: u64,
434 },
435
436 #[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")]
438 GasLimitInvalidDecrease {
439 parent_gas_limit: u64,
441 child_gas_limit: u64,
443 },
444
445 #[error(
447 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
448 )]
449 TimestampIsInPast {
450 parent_timestamp: u64,
452 timestamp: u64,
454 },
455 #[error("{0}")]
457 Other(String),
458}
459
460impl ConsensusError {
461 pub const fn is_state_root_error(&self) -> bool {
463 matches!(self, Self::BodyStateRootDiff(_))
464 }
465}
466
467impl From<InvalidTransactionError> for ConsensusError {
468 fn from(value: InvalidTransactionError) -> Self {
469 Self::InvalidTransaction(value)
470 }
471}
472
473#[derive(thiserror::Error, Debug)]
475#[error("Consensus error: {0}, Invalid header: {1:?}")]
476pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);