From e421ef1db3d147c1db375a1e1e8d1cbee82f6196 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 11 Dec 2024 13:49:58 -0500 Subject: [PATCH] Impl isthmus l1tol2-msg-passer storage root validation --- Cargo.lock | 3 ++ crates/consensus/consensus/src/lib.rs | 5 ++ crates/optimism/consensus/Cargo.toml | 4 ++ crates/optimism/consensus/src/error.rs | 33 ++++++++++++ crates/optimism/consensus/src/lib.rs | 5 +- .../consensus/src/validation/isthmus.rs | 52 +++++++++++++++++++ .../src/{validation.rs => validation/mod.rs} | 4 ++ crates/optimism/primitives/src/lib.rs | 2 + crates/optimism/primitives/src/predeploys.rs | 8 +++ 9 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 crates/optimism/consensus/src/error.rs create mode 100644 crates/optimism/consensus/src/validation/isthmus.rs rename crates/optimism/consensus/src/{validation.rs => validation/mod.rs} (97%) create mode 100644 crates/optimism/primitives/src/predeploys.rs diff --git a/Cargo.lock b/Cargo.lock index e191b69ebf53..56d00d3ff370 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8426,6 +8426,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-trie", + "derive_more", "reth-chainspec", "reth-consensus", "reth-consensus-common", @@ -8433,6 +8434,8 @@ dependencies = [ "reth-optimism-forks", "reth-optimism-primitives", "reth-primitives", + "reth-storage-api", + "reth-storage-errors", "reth-trie-common", "tracing", ] diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index ba1b1321e776..8c7b9ff3aa00 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -451,6 +451,11 @@ pub enum ConsensusError { /// The block's timestamp. timestamp: u64, }, + /// Custom error + // todo: remove in favour of AT Consensus::Error, so OpConsensusError can wrap ConsensusError + // in a variant instead + #[display("custom l2 error (search for it in debug logs)")] + Other, } impl ConsensusError { diff --git a/crates/optimism/consensus/Cargo.toml b/crates/optimism/consensus/Cargo.toml index 4f4868a454dc..57dfc7bcd680 100644 --- a/crates/optimism/consensus/Cargo.toml +++ b/crates/optimism/consensus/Cargo.toml @@ -18,6 +18,8 @@ reth-consensus-common.workspace = true reth-consensus.workspace = true reth-primitives.workspace = true reth-trie-common.workspace = true +reth-storage-api.workspace = true +reth-storage-errors.workspace = true # op-reth reth-optimism-forks.workspace = true @@ -31,6 +33,8 @@ alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-trie.workspace = true +# misc +derive_more = { workspace = true, features = ["display", "error"] } tracing.workspace = true [dev-dependencies] diff --git a/crates/optimism/consensus/src/error.rs b/crates/optimism/consensus/src/error.rs new file mode 100644 index 000000000000..a2ef287d9e7d --- /dev/null +++ b/crates/optimism/consensus/src/error.rs @@ -0,0 +1,33 @@ +//! Optimism consensus errors + +use alloy_primitives::B256; +use derive_more::{Display, Error, From}; +use reth_storage_errors::provider::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 L2toL1MessagePasser missing (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: {}, expected {expected}", got.map(|hash| hash.to_string()).unwrap_or_else(|| "null".to_string()))] + StorageRootMismatch { + /// Storage root of pre-deploy in block. + got: Option, + /// Storage root of pre-deploy loaded from local state. + expected: B256, + }, +} diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index d05ff9c9bd76..6e4167ed05dc 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -27,11 +27,14 @@ use reth_optimism_primitives::OpPrimitives; use reth_primitives::{BlockBody, BlockWithSenders, GotExpected, SealedBlock, SealedHeader}; use std::{sync::Arc, time::SystemTime}; +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::{isthmus, validate_block_post_execution}; /// Optimism consensus implementation. /// diff --git a/crates/optimism/consensus/src/validation/isthmus.rs b/crates/optimism/consensus/src/validation/isthmus.rs new file mode 100644 index 000000000000..129d910398c9 --- /dev/null +++ b/crates/optimism/consensus/src/validation/isthmus.rs @@ -0,0 +1,52 @@ +//! Block validation w.r.t. consensus rules new in isthmus hardfork. + +use reth_consensus::ConsensusError; +use reth_optimism_primitives::predeploys::ADDRESS_L2_TO_L1_MESSAGE_PASSER; +use reth_primitives::SealedHeader; +use reth_storage_api::{StateProviderFactory, StorageRootProvider}; +use tracing::{debug, trace}; + +use crate::OpConsensusError; + +/// Validates block header field `withdrawals_root` against storage root of +/// `L2-to-L1-message-passer` predeploy. +pub fn validate_l2_to_l1_msg_passer( + provider: impl StateProviderFactory, + header: &SealedHeader, +) -> Result<(), ConsensusError> { + let state = provider.latest().map_err(|err| { + debug!(target: "op::consensus", + block_number=header.number, + err=%OpConsensusError::LoadStorageRootFailed(err), + "failed to load latest state", + ); + + ConsensusError::Other + })?; + + let storage_root_msg_passer = + state.storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, Default::default()).map_err(|err| { + debug!(target: "op::consensus", + block_number=header.number, + err=%OpConsensusError::LoadStorageRootFailed(err), + "failed to load storage root for l2tol1-msg-passer predeploy", + ); + + ConsensusError::Other + })?; + + if header.withdrawals_root.is_none_or(|root| root != storage_root_msg_passer) { + trace!(target: "op::consensus", + block_number=header.number, + err=%OpConsensusError::StorageRootMismatch { + got: header.withdrawals_root, + expected: storage_root_msg_passer + }, + "block failed validation", + ); + + return Err(ConsensusError::Other) + } + + Ok(()) +} diff --git a/crates/optimism/consensus/src/validation.rs b/crates/optimism/consensus/src/validation/mod.rs similarity index 97% rename from crates/optimism/consensus/src/validation.rs rename to crates/optimism/consensus/src/validation/mod.rs index 5290603e7b89..59091aca39fd 100644 --- a/crates/optimism/consensus/src/validation.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -1,3 +1,7 @@ +//! Validation of blocks w.r.t. Optimism hardforks. + +pub mod isthmus; + use crate::proof::calculate_receipt_root_optimism; use alloy_consensus::TxReceipt; use alloy_primitives::{Bloom, B256}; diff --git a/crates/optimism/primitives/src/lib.rs b/crates/optimism/primitives/src/lib.rs index b1f029d20bc2..14aacdb2a857 100644 --- a/crates/optimism/primitives/src/lib.rs +++ b/crates/optimism/primitives/src/lib.rs @@ -14,8 +14,10 @@ extern crate alloc; pub mod bedrock; +pub mod predeploys; pub mod transaction; +pub use predeploys::ADDRESS_L2_TO_L1_MESSAGE_PASSER; pub use transaction::{signed::OpTransactionSigned, tx_type::OpTxType, OpTransaction}; /// Optimism primitive types. diff --git a/crates/optimism/primitives/src/predeploys.rs b/crates/optimism/primitives/src/predeploys.rs new file mode 100644 index 000000000000..c29f72a0daea --- /dev/null +++ b/crates/optimism/primitives/src/predeploys.rs @@ -0,0 +1,8 @@ +//! Addresses of OP pre-deploys. +// todo: move to op-alloy + +use alloy_primitives::{address, Address}; + +/// The L2 contract `L2ToL1MessagePasser`, stores commitments to withdrawal transactions. +pub const ADDRESS_L2_TO_L1_MESSAGE_PASSER: Address = + address!("4200000000000000000000000000000000000016");