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, sync::Arc, vec::Vec};
15use alloy_consensus::Header;
16use alloy_eips::eip7685::Requests;
17use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256, U256};
18use reth_primitives::{
19 BlockBody, BlockWithSenders, EthPrimitives, GotExpected, GotExpectedBoxed,
20 InvalidTransactionError, NodePrimitives, Receipt, SealedBlock, SealedHeader,
21};
22use reth_primitives_traits::constants::MINIMUM_GAS_LIMIT;
23
24pub mod noop;
26
27#[cfg(any(test, feature = "test-utils"))]
28pub mod test_utils;
30
31#[derive(Debug)]
33pub struct PostExecutionInput<'a, R = Receipt> {
34 pub receipts: &'a [R],
36 pub requests: &'a Requests,
38}
39
40impl<'a, R> PostExecutionInput<'a, R> {
41 pub const fn new(receipts: &'a [R], requests: &'a Requests) -> Self {
43 Self { receipts, requests }
44 }
45}
46
47#[auto_impl::auto_impl(&, Arc)]
50pub trait FullConsensus<N: NodePrimitives = EthPrimitives>:
51 AsConsensus<N::BlockHeader, N::BlockBody>
52{
53 fn validate_block_post_execution(
60 &self,
61 block: &BlockWithSenders<N::Block>,
62 input: PostExecutionInput<'_, N::Receipt>,
63 ) -> Result<(), ConsensusError>;
64}
65
66#[auto_impl::auto_impl(&, Arc)]
68pub trait Consensus<H = Header, B = BlockBody>: AsHeaderValidator<H> {
69 fn validate_body_against_header(
71 &self,
72 body: &B,
73 header: &SealedHeader<H>,
74 ) -> Result<(), ConsensusError>;
75
76 fn validate_block_pre_execution(&self, block: &SealedBlock<H, B>)
86 -> Result<(), ConsensusError>;
87}
88
89#[auto_impl::auto_impl(&, Arc)]
91pub trait HeaderValidator<H = Header>: Debug + Send + Sync {
92 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError>;
96
97 fn validate_header_against_parent(
108 &self,
109 header: &SealedHeader<H>,
110 parent: &SealedHeader<H>,
111 ) -> Result<(), ConsensusError>;
112
113 fn validate_header_range(
120 &self,
121 headers: &[SealedHeader<H>],
122 ) -> Result<(), HeaderConsensusError<H>>
123 where
124 H: Clone,
125 {
126 if let Some((initial_header, remaining_headers)) = headers.split_first() {
127 self.validate_header(initial_header)
128 .map_err(|e| HeaderConsensusError(e, initial_header.clone()))?;
129 let mut parent = initial_header;
130 for child in remaining_headers {
131 self.validate_header(child).map_err(|e| HeaderConsensusError(e, child.clone()))?;
132 self.validate_header_against_parent(child, parent)
133 .map_err(|e| HeaderConsensusError(e, child.clone()))?;
134 parent = child;
135 }
136 }
137 Ok(())
138 }
139
140 fn validate_header_with_total_difficulty(
147 &self,
148 header: &H,
149 total_difficulty: U256,
150 ) -> Result<(), ConsensusError>;
151}
152
153pub trait AsHeaderValidator<H>: HeaderValidator<H> {
155 fn as_header_validator<'a>(self: Arc<Self>) -> Arc<dyn HeaderValidator<H> + 'a>
157 where
158 Self: 'a;
159}
160
161impl<T: HeaderValidator<H>, H> AsHeaderValidator<H> for T {
162 fn as_header_validator<'a>(self: Arc<Self>) -> Arc<dyn HeaderValidator<H> + 'a>
163 where
164 Self: 'a,
165 {
166 self
167 }
168}
169
170pub trait AsConsensus<H, B>: Consensus<H, B> {
172 fn as_consensus<'a>(self: Arc<Self>) -> Arc<dyn Consensus<H, B> + 'a>
174 where
175 Self: 'a;
176}
177
178impl<T: Consensus<H, B>, H, B> AsConsensus<H, B> for T {
179 fn as_consensus<'a>(self: Arc<Self>) -> Arc<dyn Consensus<H, B> + 'a>
180 where
181 Self: 'a,
182 {
183 self
184 }
185}
186
187#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display, derive_more::Error)]
189pub enum ConsensusError {
190 #[display("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")]
192 HeaderGasUsedExceedsGasLimit {
193 gas_used: u64,
195 gas_limit: u64,
197 },
198
199 #[display(
201 "block gas used mismatch: {gas}; gas spent by each transaction: {gas_spent_by_tx:?}"
202 )]
203 BlockGasUsed {
204 gas: GotExpected<u64>,
206 gas_spent_by_tx: Vec<(u64, u64)>,
208 },
209
210 #[display("mismatched block ommer hash: {_0}")]
212 BodyOmmersHashDiff(GotExpectedBoxed<B256>),
213
214 #[display("mismatched block state root: {_0}")]
216 BodyStateRootDiff(GotExpectedBoxed<B256>),
217
218 #[display("mismatched block transaction root: {_0}")]
221 BodyTransactionRootDiff(GotExpectedBoxed<B256>),
222
223 #[display("receipt root mismatch: {_0}")]
225 BodyReceiptRootDiff(GotExpectedBoxed<B256>),
226
227 #[display("header bloom filter mismatch: {_0}")]
229 BodyBloomLogDiff(GotExpectedBoxed<Bloom>),
230
231 #[display("mismatched block withdrawals root: {_0}")]
234 BodyWithdrawalsRootDiff(GotExpectedBoxed<B256>),
235
236 #[display("mismatched block requests hash: {_0}")]
239 BodyRequestsHashDiff(GotExpectedBoxed<B256>),
240
241 #[display("block with [hash={hash}, number={number}] is already known")]
243 BlockKnown {
244 hash: BlockHash,
246 number: BlockNumber,
248 },
249
250 #[display("block parent [hash={hash}] is not known")]
252 ParentUnknown {
253 hash: BlockHash,
255 },
256
257 #[display(
259 "block number {block_number} does not match parent block number {parent_block_number}"
260 )]
261 ParentBlockNumberMismatch {
262 parent_block_number: BlockNumber,
264 block_number: BlockNumber,
266 },
267
268 #[display("mismatched parent hash: {_0}")]
270 ParentHashMismatch(GotExpectedBoxed<B256>),
271
272 #[display(
274 "block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}"
275 )]
276 TimestampIsInFuture {
277 timestamp: u64,
279 present_timestamp: u64,
281 },
282
283 #[display("base fee missing")]
285 BaseFeeMissing,
286
287 #[display("transaction signer recovery error")]
289 TransactionSignerRecoveryError,
290
291 #[display("extra data {len} exceeds max length")]
293 ExtraDataExceedsMax {
294 len: usize,
296 },
297
298 #[display("difficulty after merge is not zero")]
300 TheMergeDifficultyIsNotZero,
301
302 #[display("nonce after merge is not zero")]
304 TheMergeNonceIsNotZero,
305
306 #[display("ommer root after merge is not empty")]
308 TheMergeOmmerRootIsNotEmpty,
309
310 #[display("missing withdrawals root")]
312 WithdrawalsRootMissing,
313
314 #[display("missing requests hash")]
316 RequestsHashMissing,
317
318 #[display("unexpected withdrawals root")]
320 WithdrawalsRootUnexpected,
321
322 #[display("unexpected requests hash")]
324 RequestsHashUnexpected,
325
326 #[display("missing withdrawals")]
328 BodyWithdrawalsMissing,
329
330 #[display("missing requests")]
332 BodyRequestsMissing,
333
334 #[display("missing blob gas used")]
336 BlobGasUsedMissing,
337
338 #[display("unexpected blob gas used")]
340 BlobGasUsedUnexpected,
341
342 #[display("missing excess blob gas")]
344 ExcessBlobGasMissing,
345
346 #[display("unexpected excess blob gas")]
348 ExcessBlobGasUnexpected,
349
350 #[display("missing parent beacon block root")]
352 ParentBeaconBlockRootMissing,
353
354 #[display("unexpected parent beacon block root")]
356 ParentBeaconBlockRootUnexpected,
357
358 #[display("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
360 BlobGasUsedExceedsMaxBlobGasPerBlock {
361 blob_gas_used: u64,
363 max_blob_gas_per_block: u64,
365 },
366
367 #[display(
369 "blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
370 )]
371 BlobGasUsedNotMultipleOfBlobGasPerBlob {
372 blob_gas_used: u64,
374 blob_gas_per_blob: u64,
376 },
377
378 #[display(
380 "excess blob gas {excess_blob_gas} is not a multiple of blob gas per blob {blob_gas_per_blob}"
381 )]
382 ExcessBlobGasNotMultipleOfBlobGasPerBlob {
383 excess_blob_gas: u64,
385 blob_gas_per_blob: u64,
387 },
388
389 #[display("blob gas used mismatch: {_0}")]
391 BlobGasUsedDiff(GotExpected<u64>),
392
393 InvalidTransaction(InvalidTransactionError),
395
396 #[display("block base fee mismatch: {_0}")]
398 BaseFeeDiff(GotExpected<u64>),
399
400 #[display(
402 "invalid excess blob gas: {diff}; \
403 parent excess blob gas: {parent_excess_blob_gas}, \
404 parent blob gas used: {parent_blob_gas_used}"
405 )]
406 ExcessBlobGasDiff {
407 diff: GotExpected<u64>,
409 parent_excess_blob_gas: u64,
411 parent_blob_gas_used: u64,
413 },
414
415 #[display("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")]
417 GasLimitInvalidIncrease {
418 parent_gas_limit: u64,
420 child_gas_limit: u64,
422 },
423
424 #[display(
428 "child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})"
429 )]
430 GasLimitInvalidMinimum {
431 child_gas_limit: u64,
433 },
434
435 #[display("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")]
437 GasLimitInvalidDecrease {
438 parent_gas_limit: u64,
440 child_gas_limit: u64,
442 },
443
444 #[display(
446 "block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}"
447 )]
448 TimestampIsInPast {
449 parent_timestamp: u64,
451 timestamp: u64,
453 },
454}
455
456impl ConsensusError {
457 pub const fn is_state_root_error(&self) -> bool {
459 matches!(self, Self::BodyStateRootDiff(_))
460 }
461}
462
463impl From<InvalidTransactionError> for ConsensusError {
464 fn from(value: InvalidTransactionError) -> Self {
465 Self::InvalidTransaction(value)
466 }
467}
468
469#[derive(derive_more::Display, derive_more::Error, Debug)]
471#[display("Consensus error: {_0}, Invalid header: {_1:?}")]
472pub struct HeaderConsensusError<H>(ConsensusError, SealedHeader<H>);