Skip to content

Commit

Permalink
Write and validate l2tol1-message-passer in block header
Browse files Browse the repository at this point in the history
  • Loading branch information
emhane committed Dec 13, 2024
1 parent 2690d7a commit 2aba470
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 13 deletions.
38 changes: 38 additions & 0 deletions crates/optimism/consensus/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Optimism consensus errors
use alloy_primitives::B256;
use derive_more::{Display, Error, From};
use reth_consensus::ConsensusError;
use reth_storage_errors::ProviderError;

/// Optimism consensus error.
#[derive(Debug, PartialEq, Eq, Clone, Display, Error, From)]
pub enum OpConsensusError {
/// Block body has non-empty withdrawals list.
#[display("non-empty withdrawals list")]
WithdrawalsNonEmpty,
/// Failed to load storage root of
/// [`L2toL1MessagePasser`](reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER).
#[display("failed to load storage root of L2toL1MessagePasser pre-deploy: {_0}")]
#[from]
LoadStorageRootFailed(ProviderError),
/// Storage root of
/// [`L2toL1MessagePasser`](reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER) missing
/// in block (withdrawals root field).
#[display("storage root of l2tol1-msg-passer predeploy missing from block header (withdrawals root field empty)")]
StorageRootMissing,
/// Storage root of
/// [`L2toL1MessagePasser`](reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER)
/// in block (withdrawals field), doesn't match local storage root.
#[display("L2toL1MessagePasser storage root mismatch, got: {got}, expected {expected}"]
StorageRootMismatch {
/// Storage root of pre-deploy in block.
got: B256,
/// Storage root of pre-deploy loaded from local state.
expected: B256,
},
/// L1 [`ConsensusError`], that also occurs on L2.
#[display("{_0}")]
#[from]
Eth(ConsensusError),
}
72 changes: 59 additions & 13 deletions crates/optimism/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
// The `optimism` feature must be enabled to use this crate.
#![cfg(feature = "optimism")]
//#![cfg(feature = "optimism")]

use core::fmt;

use alloy_consensus::{BlockHeader, Header, EMPTY_OMMER_ROOT_HASH};
use alloy_primitives::{B64, U256};
Expand All @@ -19,37 +21,46 @@ use reth_consensus_common::validation::{
validate_against_parent_4844, validate_against_parent_eip1559_base_fee,
validate_against_parent_hash_number, validate_against_parent_timestamp,
validate_body_against_header, validate_cancun_gas, validate_header_base_fee,
validate_header_extradata, validate_header_gas, validate_shanghai_withdrawals,
validate_header_extradata, validate_header_gas,
};
use reth_optimism_chainspec::OpChainSpec;
use reth_optimism_forks::OpHardforks;
use reth_optimism_primitives::OpPrimitives;
use reth_primitives::{BlockBody, BlockWithSenders, GotExpected, SealedBlock, SealedHeader};
use reth_storage_api::StateProviderFactory;
use std::{sync::Arc, time::SystemTime};
use tracing::trace;

pub mod error;
pub use error::OpConsensusError;

mod proof;
pub use proof::calculate_receipt_root_no_memo_optimism;

mod validation;
pub use validation::validate_block_post_execution;
pub use validation::{canyon, isthmus, validate_block_post_execution};

/// Optimism consensus implementation.
///
/// Provides basic checks as outlined in the execution specs.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpBeaconConsensus {
pub struct OpBeaconConsensus<P> {
/// Configuration
chain_spec: Arc<OpChainSpec>,
provider: P,
}

impl OpBeaconConsensus {
impl<P> OpBeaconConsensus<P> {
/// Create a new instance of [`OpBeaconConsensus`]
pub const fn new(chain_spec: Arc<OpChainSpec>) -> Self {
Self { chain_spec }
pub const fn new(chain_spec: Arc<OpChainSpec>, provider: P) -> Self {
Self { chain_spec, provider }
}
}

impl FullConsensus<OpPrimitives> for OpBeaconConsensus {
impl<P> FullConsensus<OpPrimitives> for OpBeaconConsensus<P>
where
P: StateProviderFactory + fmt::Debug,
{
fn validate_block_post_execution(
&self,
block: &BlockWithSenders,
Expand All @@ -59,7 +70,10 @@ impl FullConsensus<OpPrimitives> for OpBeaconConsensus {
}
}

impl Consensus for OpBeaconConsensus {
impl<P> Consensus for OpBeaconConsensus<P>
where
P: StateProviderFactory + fmt::Debug,
{
fn validate_body_against_header(
&self,
body: &BlockBody,
Expand All @@ -82,20 +96,52 @@ impl Consensus for OpBeaconConsensus {
return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
}

// EIP-4895: Beacon chain push withdrawals as operations
if self.chain_spec.is_shanghai_active_at_timestamp(block.timestamp) {
validate_shanghai_withdrawals(block)?;
// Check empty shanghai-withdrawals
if self.chain_spec.is_canyon_active_at_timestamp(block.timestamp) {
canyon::validate_empty_shanghai_withdrawals(&block.body).map_err(|err| {
trace!(target: "op::consensus",
block_number=block.number(),
%err,
"block failed validation",
);

ConsensusError::Other
})?;
} else {
return Ok(())
}

if self.chain_spec.is_cancun_active_at_timestamp(block.timestamp) {
validate_cancun_gas(block)?;
} else {
return Ok(())
}

if self.chain_spec.is_isthmus_active_at_timestamp(block.timestamp) {
isthmus::validate_l2_to_l1_msg_passer(&self.provider, &block.header).map_err(
|err| {
trace!(target: "op::consensus",
block_number=block.number(),
%err,
"block failed validation",
);

ConsensusError::Other
},
)?;
} else {
// canyon is active, else would already have returned
canyon::validate_empty_withdrawals_root(&block.header)?;
}

Ok(())
}
}

impl HeaderValidator for OpBeaconConsensus {
impl<P> HeaderValidator for OpBeaconConsensus<P>
where
P: Send + Sync + fmt::Debug,
{
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
validate_header_gas(header.header())?;
validate_header_base_fee(header.header(), &self.chain_spec)
Expand Down
42 changes: 42 additions & 0 deletions crates/optimism/consensus/src/validation/canyon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Canyon consensus rule checks.
use alloy_consensus::BlockHeader;
use alloy_trie::EMPTY_ROOT_HASH;
use reth_consensus::ConsensusError;
use reth_primitives::GotExpected;
use reth_primitives_traits::BlockBody;

use crate::OpConsensusError;

/// Validate that withdrawals in block body (Shanghai) is always empty in Canyon.
// todo: link OP docs
#[inline]
pub fn validate_empty_shanghai_withdrawals<B: BlockBody>(body: &B) -> Result<(), OpConsensusError> {
// Shanghai rule
let withdrawals = body.withdrawals().ok_or(ConsensusError::BodyWithdrawalsMissing)?;

// Canyon rule
if !withdrawals.is_empty() {
return Err(OpConsensusError::WithdrawalsNonEmpty)
}

Ok(())
}

/// Validate that withdrawals root in block header (Shanghai) is always [`EMPTY_ROOT_HASH`] in
/// Canyon.
#[inline]
pub fn validate_empty_withdrawals_root<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
// Shanghai rule
let header_withdrawals_root =
header.withdrawals_root().ok_or(ConsensusError::WithdrawalsRootMissing)?;

// Canyon rules
if header_withdrawals_root != EMPTY_ROOT_HASH {
return Err(ConsensusError::BodyWithdrawalsRootDiff(
GotExpected { got: header_withdrawals_root, expected: EMPTY_ROOT_HASH }.into(),
));
}

Ok(())
}
32 changes: 32 additions & 0 deletions crates/optimism/consensus/src/validation/isthmus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Block validation w.r.t. consensus rules new in isthmus hardfork.
use alloy_consensus::BlockHeader;
use reth_optimism_primitives::predeploys::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
use reth_storage_api::{StateProviderFactory, StorageRootProvider};

use crate::OpConsensusError;

/// Validates block header field `withdrawals_root` against storage root of
/// `2toL1-message-passer` predeploy.
pub fn validate_l2_to_l1_msg_passer<H: BlockHeader, P: StateProviderFactory>(
provider: &P,
header: &H,
) -> Result<(), OpConsensusError> {
let header_storage_root =
header.withdrawals_root().ok_or(OpConsensusError::StorageRootMissing)?;

let state = provider.latest().map_err(OpConsensusError::LoadStorageRootFailed)?;

let storage_root = state
.storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, Default::default())
.map_err(OpConsensusError::LoadStorageRootFailed)?;

if header_storage_root != storage_root {
return Err(OpConsensusError::StorageRootMismatch {
got: header_storage_root,
expected: storage_root,
})
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! Validation of blocks w.r.t. Optimism hardforks.
pub mod isthmus;
pub mod canyon;

use crate::proof::calculate_receipt_root_optimism;
use alloy_consensus::TxReceipt;
use alloy_primitives::{Bloom, B256};
Expand Down

0 comments on commit 2aba470

Please sign in to comment.