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/SeismicSystems/seismic-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, 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>;
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
126#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
128pub enum ConsensusError {
129 #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
131 HeaderGasUsedExceedsGasLimit {
132 gas_used: u64,
134 gas_limit: u64,
136 },
137 #[error(
139 "header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
140 )]
141 HeaderGasLimitExceedsMax {
142 gas_limit: u64,
144 },
145
146 #[error("block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}")]
148 BlockGasUsed {
149 gas: GotExpected<u64>,
151 gas_spent_by_tx: Vec<(u64, u64)>,
153 },
154
155 #[error("mismatched block ommer hash: {0}")]
157 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
158
159 #[error("mismatched block state root: {0}")]
161 BodyStateRootDiff(GotExpectedBoxed<B256>),
162
163 #[error("mismatched block transaction root: {0}")]
166 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
167
168 #[error("receipt root mismatch: {0}")]
170 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
171
172 #[error("header bloom filter mismatch: {0}")]
174 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
175
176 #[error("mismatched block withdrawals root: {0}")]
179 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
180
181 #[error("mismatched block requests hash: {0}")]
184 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
185
186 #[error("block with [hash={hash}, number={number}] is already known")]
188 BlockKnown {
189 hash: BlockHash,
191 number: BlockNumber,
193 },
194
195 #[error("block parent [hash={hash}] is not known")]
197 ParentUnknown {
198 hash: BlockHash,
200 },
201
202 #[error(
204 "block number {block_number} does not match parent block number {parent_block_number}"
205 )]
206 ParentBlockNumberMismatch {
207 parent_block_number: BlockNumber,
209 block_number: BlockNumber,
211 },
212
213 #[error("mismatched parent hash: {0}")]
215 ParentHashMismatch(GotExpectedBoxed<B256>),
216
217 #[error(
219 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
220 )]
221 TimestampIsInFuture {
222 timestamp: u64,
224 present_timestamp: u64,
226 },
227
228 #[error("base fee missing")]
230 BaseFeeMissing,
231
232 #[error("transaction signer recovery error")]
234 TransactionSignerRecoveryError,
235
236 #[error("extra data {len} exceeds max length")]
238 ExtraDataExceedsMax {
239 len: usize,
241 },
242
243 #[error("difficulty after merge is not zero")]
245 TheMergeDifficultyIsNotZero,
246
247 #[error("nonce after merge is not zero")]
249 TheMergeNonceIsNotZero,
250
251 #[error("ommer root after merge is not empty")]
253 TheMergeOmmerRootIsNotEmpty,
254
255 #[error("missing withdrawals root")]
257 WithdrawalsRootMissing,
258
259 #[error("missing requests hash")]
261 RequestsHashMissing,
262
263 #[error("unexpected withdrawals root")]
265 WithdrawalsRootUnexpected,
266
267 #[error("unexpected requests hash")]
269 RequestsHashUnexpected,
270
271 #[error("missing withdrawals")]
273 BodyWithdrawalsMissing,
274
275 #[error("missing requests")]
277 BodyRequestsMissing,
278
279 #[error("missing blob gas used")]
281 BlobGasUsedMissing,
282
283 #[error("unexpected blob gas used")]
285 BlobGasUsedUnexpected,
286
287 #[error("missing excess blob gas")]
289 ExcessBlobGasMissing,
290
291 #[error("unexpected excess blob gas")]
293 ExcessBlobGasUnexpected,
294
295 #[error("missing parent beacon block root")]
297 ParentBeaconBlockRootMissing,
298
299 #[error("unexpected parent beacon block root")]
301 ParentBeaconBlockRootUnexpected,
302
303 #[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
305 BlobGasUsedExceedsMaxBlobGasPerBlock {
306 blob_gas_used: u64,
308 max_blob_gas_per_block: u64,
310 },
311
312 #[error(
314 "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
315 )]
316 BlobGasUsedNotMultipleOfBlobGasPerBlob {
317 blob_gas_used: u64,
319 blob_gas_per_blob: u64,
321 },
322
323 #[error(
325 "excess blob gas {excess_blob_gas} is not a multiple of blob gas per blob {blob_gas_per_blob}"
326 )]
327 ExcessBlobGasNotMultipleOfBlobGasPerBlob {
328 excess_blob_gas: u64,
330 blob_gas_per_blob: u64,
332 },
333
334 #[error("blob gas used mismatch: {0}")]
336 BlobGasUsedDiff(GotExpected<u64>),
337
338 #[error(transparent)]
340 InvalidTransaction(InvalidTransactionError),
341
342 #[error("block base fee mismatch: {0}")]
344 BaseFeeDiff(GotExpected<u64>),
345
346 #[error(
348 "invalid excess blob gas: {diff}; \
349 parent excess blob gas: {parent_excess_blob_gas}, \
350 parent blob gas used: {parent_blob_gas_used}"
351 )]
352 ExcessBlobGasDiff {
353 diff: GotExpected<u64>,
355 parent_excess_blob_gas: u64,
357 parent_blob_gas_used: u64,
359 },
360
361 #[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")]
363 GasLimitInvalidIncrease {
364 parent_gas_limit: u64,
366 child_gas_limit: u64,
368 },
369
370 #[error(
374 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
375 )]
376 GasLimitInvalidMinimum {
377 child_gas_limit: u64,
379 },
380
381 #[error("child gas limit {block_gas_limit} is above the maximum allowed limit ({MAXIMUM_GAS_LIMIT_BLOCK})")]
385 GasLimitInvalidBlockMaximum {
386 block_gas_limit: u64,
388 },
389
390 #[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")]
392 GasLimitInvalidDecrease {
393 parent_gas_limit: u64,
395 child_gas_limit: u64,
397 },
398
399 #[error(
401 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
402 )]
403 TimestampIsInPast {
404 parent_timestamp: u64,
406 timestamp: u64,
408 },
409 #[error("{0}")]
411 Other(String),
412}
413
414impl ConsensusError {
415 pub const fn is_state_root_error(&self) -> bool {
417 matches!(self, Self::BodyStateRootDiff(_))
418 }
419}
420
421impl From<InvalidTransactionError> for ConsensusError {
422 fn from(value: InvalidTransactionError) -> Self {
423 Self::InvalidTransaction(value)
424 }
425}
426
427#[derive(thiserror::Error, Debug)]
429#[error("Consensus error: {0}, Invalid header: {1:?}")]
430pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);