reth_optimism_consensus/
lib.rs

1//! Optimism Consensus implementation.
2
3#![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(docsrs, feature(doc_cfg, doc_auto_cfg))]
9#![cfg_attr(not(feature = "std"), no_std)]
10#![cfg_attr(not(test), warn(unused_crate_dependencies))]
11
12extern crate alloc;
13
14use alloc::{format, sync::Arc};
15use alloy_consensus::{BlockHeader as _, EMPTY_OMMER_ROOT_HASH};
16use alloy_primitives::B64;
17use core::fmt::Debug;
18use reth_chainspec::EthChainSpec;
19use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator};
20use reth_consensus_common::validation::{
21    validate_against_parent_4844, validate_against_parent_eip1559_base_fee,
22    validate_against_parent_hash_number, validate_against_parent_timestamp, validate_cancun_gas,
23    validate_header_base_fee, validate_header_extra_data, validate_header_gas,
24};
25use reth_execution_types::BlockExecutionResult;
26use reth_optimism_forks::OpHardforks;
27use reth_optimism_primitives::DepositReceipt;
28use reth_primitives_traits::{
29    Block, BlockBody, BlockHeader, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock,
30    SealedHeader,
31};
32
33mod proof;
34pub use proof::calculate_receipt_root_no_memo_optimism;
35
36pub mod validation;
37pub use validation::{canyon, isthmus, validate_block_post_execution};
38
39pub mod error;
40pub use error::OpConsensusError;
41
42/// Optimism consensus implementation.
43///
44/// Provides basic checks as outlined in the execution specs.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct OpBeaconConsensus<ChainSpec> {
47    /// Configuration
48    chain_spec: Arc<ChainSpec>,
49}
50
51impl<ChainSpec> OpBeaconConsensus<ChainSpec> {
52    /// Create a new instance of [`OpBeaconConsensus`]
53    pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
54        Self { chain_spec }
55    }
56}
57
58impl<N, ChainSpec> FullConsensus<N> for OpBeaconConsensus<ChainSpec>
59where
60    N: NodePrimitives<Receipt: DepositReceipt>,
61    ChainSpec: EthChainSpec<Header = N::BlockHeader> + OpHardforks + Debug + Send + Sync,
62{
63    fn validate_block_post_execution(
64        &self,
65        block: &RecoveredBlock<N::Block>,
66        result: &BlockExecutionResult<N::Receipt>,
67    ) -> Result<(), ConsensusError> {
68        validate_block_post_execution(block.header(), &self.chain_spec, &result.receipts)
69    }
70}
71
72impl<B, ChainSpec> Consensus<B> for OpBeaconConsensus<ChainSpec>
73where
74    B: Block,
75    ChainSpec: EthChainSpec<Header = B::Header> + OpHardforks + Debug + Send + Sync,
76{
77    type Error = ConsensusError;
78
79    fn validate_body_against_header(
80        &self,
81        body: &B::Body,
82        header: &SealedHeader<B::Header>,
83    ) -> Result<(), ConsensusError> {
84        validation::validate_body_against_header_op(&self.chain_spec, body, header.header())
85    }
86
87    fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError> {
88        // Check ommers hash
89        let ommers_hash = block.body().calculate_ommers_root();
90        if Some(block.ommers_hash()) != ommers_hash {
91            return Err(ConsensusError::BodyOmmersHashDiff(
92                GotExpected {
93                    got: ommers_hash.unwrap_or(EMPTY_OMMER_ROOT_HASH),
94                    expected: block.ommers_hash(),
95                }
96                .into(),
97            ))
98        }
99
100        // Check transaction root
101        if let Err(error) = block.ensure_transaction_root_valid() {
102            return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
103        }
104
105        // Check empty shanghai-withdrawals
106        if self.chain_spec.is_canyon_active_at_timestamp(block.timestamp()) {
107            canyon::ensure_empty_shanghai_withdrawals(block.body()).map_err(|err| {
108                ConsensusError::Other(format!("failed to verify block {}: {err}", block.number()))
109            })?
110        } else {
111            return Ok(())
112        }
113
114        if self.chain_spec.is_ecotone_active_at_timestamp(block.timestamp()) {
115            validate_cancun_gas(block)?;
116        }
117
118        // Check withdrawals root field in header
119        if self.chain_spec.is_isthmus_active_at_timestamp(block.timestamp()) {
120            // storage root of withdrawals pre-deploy is verified post-execution
121            isthmus::ensure_withdrawals_storage_root_is_some(block.header()).map_err(|err| {
122                ConsensusError::Other(format!("failed to verify block {}: {err}", block.number()))
123            })?
124        } else {
125            // canyon is active, else would have returned already
126            canyon::ensure_empty_withdrawals_root(block.header())?
127        }
128
129        Ok(())
130    }
131}
132
133impl<H, ChainSpec> HeaderValidator<H> for OpBeaconConsensus<ChainSpec>
134where
135    H: BlockHeader,
136    ChainSpec: EthChainSpec<Header = H> + OpHardforks + Debug + Send + Sync,
137{
138    fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError> {
139        let header = header.header();
140        // with OP-stack Bedrock activation number determines when TTD (eth Merge) has been reached.
141        debug_assert!(
142            self.chain_spec.is_bedrock_active_at_block(header.number()),
143            "manually import OVM blocks"
144        );
145
146        if header.nonce() != Some(B64::ZERO) {
147            return Err(ConsensusError::TheMergeNonceIsNotZero)
148        }
149
150        if header.ommers_hash() != EMPTY_OMMER_ROOT_HASH {
151            return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty)
152        }
153
154        // Post-merge, the consensus layer is expected to perform checks such that the block
155        // timestamp is a function of the slot. This is different from pre-merge, where blocks
156        // are only allowed to be in the future (compared to the system's clock) by a certain
157        // threshold.
158        //
159        // Block validation with respect to the parent should ensure that the block timestamp
160        // is greater than its parent timestamp.
161
162        // validate header extra data for all networks post merge
163        validate_header_extra_data(header)?;
164        validate_header_gas(header)?;
165        validate_header_base_fee(header, &self.chain_spec)
166    }
167
168    fn validate_header_against_parent(
169        &self,
170        header: &SealedHeader<H>,
171        parent: &SealedHeader<H>,
172    ) -> Result<(), ConsensusError> {
173        validate_against_parent_hash_number(header.header(), parent)?;
174
175        if self.chain_spec.is_bedrock_active_at_block(header.number()) {
176            validate_against_parent_timestamp(header.header(), parent.header())?;
177        }
178
179        validate_against_parent_eip1559_base_fee(
180            header.header(),
181            parent.header(),
182            &self.chain_spec,
183        )?;
184
185        // ensure that the blob gas fields for this block
186        if let Some(blob_params) = self.chain_spec.blob_params_at_timestamp(header.timestamp()) {
187            validate_against_parent_4844(header.header(), parent.header(), blob_params)?;
188        }
189
190        Ok(())
191    }
192}