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, vec::Vec};
15use alloy_consensus::Header;
16use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
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>: Consensus<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>: HeaderValidator<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>;
73}
74
75#[auto_impl::auto_impl(&, Arc)]
77pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
78 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
82
83 fn validate_header_against_parent(
94 &self,
95 header: &SealedHeader<H>,
96 parent: &SealedHeader<H>,
97 ) -> Result<(), ConsensusError>;
98
99 fn validate_header_range(
106 &self,
107 headers: &[SealedHeader<H>],
108 ) -> Result<(), HeaderConsensusError<H>>
109 where
110 H: Clone,
111 {
112 if let Some((initial_header, remaining_headers)) = headers.split_first() {
113 self.validate_header(initial_header)
114 .map_err(|e| HeaderConsensusError(e, initial_header.clone()))?;
115 let mut parent = initial_header;
116 for child in remaining_headers {
117 self.validate_header(child).map_err(|e| HeaderConsensusError(e, child.clone()))?;
118 self.validate_header_against_parent(child, parent)
119 .map_err(|e| HeaderConsensusError(e, child.clone()))?;
120 parent = child;
121 }
122 }
123 Ok(())
124 }
125}
126
127#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
129pub enum ConsensusError {
130 #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
132 HeaderGasUsedExceedsGasLimit {
133 gas_used: u64,
135 gas_limit: u64,
137 },
138 #[error(
140 "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
141 )]
142 HeaderGasLimitExceedsMax {
143 gas_limit: u64,
145 },
146
147 #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
149 BlockGasUsed {
150 gas: GotExpected<u64>,
152 gas_spent_by_tx: Vec<(u64, u64)>,
154 },
155
156 #[error("mismatched block ommer hash: {0}")]
158 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
159
160 #[error("mismatched block state root: {0}")]
162 BodyStateRootDiff(GotExpectedBoxed<B256>),
163
164 #[error("mismatched block transaction root: {0}")]
167 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
168
169 #[error("receipt root mismatch: {0}")]
171 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
172
173 #[error("header bloom filter mismatch: {0}")]
175 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
176
177 #[error("mismatched block withdrawals root: {0}")]
180 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
181
182 #[error("mismatched block requests hash: {0}")]
185 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
186
187 #[error("block with [hash={hash}, number={number}] is already known")]
189 BlockKnown {
190 hash: BlockHash,
192 number: BlockNumber,
194 },
195
196 #[error("block parent [hash={hash}] is not known")]
198 ParentUnknown {
199 hash: BlockHash,
201 },
202
203 #[error(
205 "block number {block_number} does not match parent block number {parent_block_number}"
206 )]
207 ParentBlockNumberMismatch {
208 parent_block_number: BlockNumber,
210 block_number: BlockNumber,
212 },
213
214 #[error("mismatched parent hash: {0}")]
216 ParentHashMismatch(GotExpectedBoxed<B256>),
217
218 #[error(
220 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
221 )]
222 TimestampIsInFuture {
223 timestamp: u64,
225 present_timestamp: u64,
227 },
228
229 #[error("base fee missing")]
231 BaseFeeMissing,
232
233 #[error("transaction signer recovery error")]
235 TransactionSignerRecoveryError,
236
237 #[error("extra data {len} exceeds max length")]
239 ExtraDataExceedsMax {
240 len: usize,
242 },
243
244 #[error("difficulty after merge is not zero")]
246 TheMergeDifficultyIsNotZero,
247
248 #[error("nonce after merge is not zero")]
250 TheMergeNonceIsNotZero,
251
252 #[error("ommer root after merge is not empty")]
254 TheMergeOmmerRootIsNotEmpty,
255
256 #[error("missing withdrawals root")]
258 WithdrawalsRootMissing,
259
260 #[error("missing requests hash")]
262 RequestsHashMissing,
263
264 #[error("unexpected withdrawals root")]
266 WithdrawalsRootUnexpected,
267
268 #[error("unexpected requests hash")]
270 RequestsHashUnexpected,
271
272 #[error("missing withdrawals")]
274 BodyWithdrawalsMissing,
275
276 #[error("missing requests")]
278 BodyRequestsMissing,
279
280 #[error("missing blob gas used")]
282 BlobGasUsedMissing,
283
284 #[error("unexpected blob gas used")]
286 BlobGasUsedUnexpected,
287
288 #[error("missing excess blob gas")]
290 ExcessBlobGasMissing,
291
292 #[error("unexpected excess blob gas")]
294 ExcessBlobGasUnexpected,
295
296 #[error("missing parent beacon block root")]
298 ParentBeaconBlockRootMissing,
299
300 #[error("unexpected parent beacon block root")]
302 ParentBeaconBlockRootUnexpected,
303
304 #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
306 BlobGasUsedExceedsMaxBlobGasPerBlock {
307 blob_gas_used: u64,
309 max_blob_gas_per_block: u64,
311 },
312
313 #[error(
315 "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
316 )]
317 BlobGasUsedNotMultipleOfBlobGasPerBlob {
318 blob_gas_used: u64,
320 blob_gas_per_blob: u64,
322 },
323
324 #[error("blob gas used mismatch: {0}")]
326 BlobGasUsedDiff(GotExpected<u64>),
327
328 #[error(transparent)]
330 InvalidTransaction(InvalidTransactionError),
331
332 #[error("block base fee mismatch: {0}")]
334 BaseFeeDiff(GotExpected<u64>),
335
336 #[error(
338 "invalid excess blob gas: {diff}; \
339 parent excess blob gas: {parent_excess_blob_gas}, \
340 parent blob gas used: {parent_blob_gas_used}"
341 )]
342 ExcessBlobGasDiff {
343 diff: GotExpected<u64>,
345 parent_excess_blob_gas: u64,
347 parent_blob_gas_used: u64,
349 },
350
351 #[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")]
353 GasLimitInvalidIncrease {
354 parent_gas_limit: u64,
356 child_gas_limit: u64,
358 },
359
360 #[error(
364 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
365 )]
366 GasLimitInvalidMinimum {
367 child_gas_limit: u64,
369 },
370
371 #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
375 GasLimitInvalidBlockMaximum {
376 block_gas_limit: u64,
378 },
379
380 #[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")]
382 GasLimitInvalidDecrease {
383 parent_gas_limit: u64,
385 child_gas_limit: u64,
387 },
388
389 #[error(
391 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
392 )]
393 TimestampIsInPast {
394 parent_timestamp: u64,
396 timestamp: u64,
398 },
399 #[error("block is too large: {rlp_length} > {max_rlp_length}")]
401 BlockTooLarge {
402 rlp_length: usize,
404 max_rlp_length: usize,
406 },
407 #[error(transparent)]
409 TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
410 #[error("{0}")]
412 Other(String),
413}
414
415impl ConsensusError {
416 pub const fn is_state_root_error(&self) -> bool {
418 matches!(self, Self::BodyStateRootDiff(_))
419 }
420}
421
422impl From<InvalidTransactionError> for ConsensusError {
423 fn from(value: InvalidTransactionError) -> Self {
424 Self::InvalidTransaction(value)
425 }
426}
427
428impl From<TxGasLimitTooHighErr> for ConsensusError {
429 fn from(value: TxGasLimitTooHighErr) -> Self {
430 Self::TransactionGasLimitTooHigh(Box::new(value))
431 }
432}
433
434#[derive(thiserror::Error, Debug)]
436#[error("Consensus error: {0}, Invalid header: {1:?}")]
437pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);
438
439#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
441#[error("transaction gas limit ({gas_limit}) is greater than the cap ({max_allowed})")]
442pub struct TxGasLimitTooHighErr {
443 pub tx_hash: B256,
445 pub gas_limit: u64,
447 pub max_allowed: u64,
449}