reth_optimism_consensus/
lib.rs1#![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#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct OpBeaconConsensus<ChainSpec> {
47 chain_spec: Arc<ChainSpec>,
49}
50
51impl<ChainSpec> OpBeaconConsensus<ChainSpec> {
52 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 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 if let Err(error) = block.ensure_transaction_root_valid() {
102 return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
103 }
104
105 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 if self.chain_spec.is_isthmus_active_at_timestamp(block.timestamp()) {
120 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::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 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 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 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}