feat(l2-withdrawals): Move l2 withdrawals root computation into reth-optimism-storage (#14610)

This commit is contained in:
Emilia Hane
2025-02-20 13:34:08 +01:00
committed by GitHub
parent 8165659200
commit 2a8f5b883b
9 changed files with 72 additions and 37 deletions

7
Cargo.lock generated
View File

@ -8561,6 +8561,7 @@ dependencies = [
"reth-optimism-forks", "reth-optimism-forks",
"reth-optimism-node", "reth-optimism-node",
"reth-optimism-primitives", "reth-optimism-primitives",
"reth-optimism-storage",
"reth-primitives-traits", "reth-primitives-traits",
"reth-provider", "reth-provider",
"reth-revm", "reth-revm",
@ -8695,6 +8696,7 @@ dependencies = [
"reth-optimism-evm", "reth-optimism-evm",
"reth-optimism-forks", "reth-optimism-forks",
"reth-optimism-primitives", "reth-optimism-primitives",
"reth-optimism-storage",
"reth-payload-builder", "reth-payload-builder",
"reth-payload-builder-primitives", "reth-payload-builder-primitives",
"reth-payload-primitives", "reth-payload-primitives",
@ -8704,7 +8706,6 @@ dependencies = [
"reth-provider", "reth-provider",
"reth-revm", "reth-revm",
"reth-transaction-pool", "reth-transaction-pool",
"reth-trie",
"revm", "revm",
"sha2 0.10.8", "sha2 0.10.8",
"thiserror 2.0.11", "thiserror 2.0.11",
@ -8797,11 +8798,15 @@ dependencies = [
name = "reth-optimism-storage" name = "reth-optimism-storage"
version = "1.2.0" version = "1.2.0"
dependencies = [ dependencies = [
"alloy-primitives",
"reth-codecs", "reth-codecs",
"reth-db-api", "reth-db-api",
"reth-primitives", "reth-primitives",
"reth-prune-types", "reth-prune-types",
"reth-stages-types", "reth-stages-types",
"reth-storage-api",
"reth-trie-common",
"revm",
] ]
[[package]] [[package]]

View File

@ -27,6 +27,7 @@ reth-optimism-forks.workspace = true
reth-optimism-chainspec.workspace = true reth-optimism-chainspec.workspace = true
# remove this after feature cleanup # remove this after feature cleanup
reth-optimism-primitives = { workspace = true, features = ["serde", "reth-codec"] } reth-optimism-primitives = { workspace = true, features = ["serde", "reth-codec"] }
reth-optimism-storage.workspace = true
# ethereum # ethereum
alloy-eips.workspace = true alloy-eips.workspace = true
@ -65,6 +66,7 @@ std = [
"reth-storage-api/std", "reth-storage-api/std",
"reth-storage-errors/std", "reth-storage-errors/std",
"reth-trie-common/std", "reth-trie-common/std",
"reth-optimism-storage/std",
"alloy-chains/std", "alloy-chains/std",
"alloy-eips/std", "alloy-eips/std",
"alloy-primitives/std", "alloy-primitives/std",

View File

@ -3,10 +3,9 @@
use crate::OpConsensusError; use crate::OpConsensusError;
use alloy_consensus::BlockHeader; use alloy_consensus::BlockHeader;
use core::fmt; use core::fmt;
use reth_optimism_primitives::predeploys::ADDRESS_L2_TO_L1_MESSAGE_PASSER; use reth_optimism_storage::predeploys::withdrawals_root;
use reth_storage_api::StorageRootProvider; use reth_storage_api::StorageRootProvider;
use reth_trie_common::HashedStorage; use revm::database::BundleState;
use revm::database::BundleAccount;
/// Verifies that `withdrawals_root` (i.e. `l2tol1-msg-passer` storage root since Isthmus) field is /// Verifies that `withdrawals_root` (i.e. `l2tol1-msg-passer` storage root since Isthmus) field is
/// set in block header. /// set in block header.
@ -23,7 +22,7 @@ pub fn ensure_withdrawals_storage_root_is_some<H: BlockHeader>(
/// ///
/// See <https://specs.optimism.io/protocol/isthmus/exec-engine.html#l2tol1messagepasser-storage-root-in-header>. /// See <https://specs.optimism.io/protocol/isthmus/exec-engine.html#l2tol1messagepasser-storage-root-in-header>.
pub fn verify_withdrawals_storage_root<DB, H>( pub fn verify_withdrawals_storage_root<DB, H>(
predeploy_account_updates: Option<&BundleAccount>, state_updates: &BundleState,
state: DB, state: DB,
header: H, header: H,
) -> Result<(), OpConsensusError> ) -> Result<(), OpConsensusError>
@ -34,17 +33,7 @@ where
let header_storage_root = let header_storage_root =
header.withdrawals_root().ok_or(OpConsensusError::L2WithdrawalsRootMissing)?; header.withdrawals_root().ok_or(OpConsensusError::L2WithdrawalsRootMissing)?;
// if block contained l2 withdrawals transactions, use predeploy storage updates from let storage_root = withdrawals_root(state_updates, state)
// execution
let hashed_storage_updates = predeploy_account_updates.map(|acc| {
HashedStorage::from_plain_storage(
acc.status,
acc.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
)
});
let storage_root = state
.storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, hashed_storage_updates.unwrap_or_default())
.map_err(OpConsensusError::L2WithdrawalsRootCalculationFail)?; .map_err(OpConsensusError::L2WithdrawalsRootCalculationFail)?;
if header_storage_root != storage_root { if header_storage_root != storage_root {
@ -68,12 +57,13 @@ mod test {
use reth_db_common::init::init_genesis; use reth_db_common::init::init_genesis;
use reth_optimism_chainspec::OpChainSpecBuilder; use reth_optimism_chainspec::OpChainSpecBuilder;
use reth_optimism_node::OpNode; use reth_optimism_node::OpNode;
use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
use reth_provider::{ use reth_provider::{
providers::BlockchainProvider, test_utils::create_test_provider_factory_with_node_types, providers::BlockchainProvider, test_utils::create_test_provider_factory_with_node_types,
StateWriter, StateWriter,
}; };
use reth_storage_api::StateProviderFactory; use reth_storage_api::StateProviderFactory;
use reth_trie::test_utils::storage_root_prehashed; use reth_trie::{test_utils::storage_root_prehashed, HashedStorage};
use reth_trie_common::HashedPostState; use reth_trie_common::HashedPostState;
#[test] #[test]
@ -118,7 +108,7 @@ mod test {
// validate block against existing state by passing empty state updates // validate block against existing state by passing empty state updates
verify_withdrawals_storage_root( verify_withdrawals_storage_root(
None, &BundleState::default(),
state_provider_factory.latest().expect("load state"), state_provider_factory.latest().expect("load state"),
&header, &header,
) )

View File

@ -27,13 +27,13 @@ reth-payload-util.workspace = true
reth-payload-primitives = { workspace = true, features = ["op"] } reth-payload-primitives = { workspace = true, features = ["op"] }
reth-basic-payload-builder.workspace = true reth-basic-payload-builder.workspace = true
reth-chain-state.workspace = true reth-chain-state.workspace = true
reth-trie.workspace = true
# op-reth # op-reth
reth-optimism-consensus.workspace = true reth-optimism-consensus.workspace = true
reth-optimism-evm.workspace = true reth-optimism-evm.workspace = true
reth-optimism-forks.workspace = true reth-optimism-forks.workspace = true
reth-optimism-primitives.workspace = true reth-optimism-primitives.workspace = true
reth-optimism-storage.workspace = true
# ethereum # ethereum
revm.workspace = true revm.workspace = true

View File

@ -28,9 +28,8 @@ use reth_execution_types::ExecutionOutcome;
use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism;
use reth_optimism_evm::{OpReceiptBuilder, ReceiptBuilderCtx}; use reth_optimism_evm::{OpReceiptBuilder, ReceiptBuilderCtx};
use reth_optimism_forks::OpHardforks; use reth_optimism_forks::OpHardforks;
use reth_optimism_primitives::{ use reth_optimism_primitives::transaction::signed::OpTransaction;
transaction::signed::OpTransaction, ADDRESS_L2_TO_L1_MESSAGE_PASSER, use reth_optimism_storage::predeploys;
};
use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_builder_primitives::PayloadBuilderError;
use reth_payload_primitives::PayloadBuilderAttributes; use reth_payload_primitives::PayloadBuilderAttributes;
use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions};
@ -49,7 +48,6 @@ use reth_revm::{
witness::ExecutionWitnessRecord, witness::ExecutionWitnessRecord,
}; };
use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool};
use reth_trie::HashedStorage;
use revm::{ use revm::{
context_interface::{ context_interface::{
result::{ExecutionResult, ResultAndState}, result::{ExecutionResult, ResultAndState},
@ -400,22 +398,9 @@ impl<Txs> OpBuilder<'_, Txs> {
state.merge_transitions(BundleRetention::Reverts); state.merge_transitions(BundleRetention::Reverts);
let withdrawals_root = if ctx.is_isthmus_active() { let withdrawals_root = if ctx.is_isthmus_active() {
let hashed_storage_updates =
state.bundle_state.state().get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER).map(|account| {
// block contained withdrawals transactions, use predeploy storage updates from
// execution
HashedStorage::from_plain_storage(
account.status,
account.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
)
});
// withdrawals root field in block header is used for storage root of L2 predeploy // withdrawals root field in block header is used for storage root of L2 predeploy
// `l2tol1-message-passer` // `l2tol1-message-passer`
Some(state.database.as_ref().storage_root( Some(predeploys::withdrawals_root(&state.bundle_state, state.database.as_ref())?)
ADDRESS_L2_TO_L1_MESSAGE_PASSER,
hashed_storage_updates.unwrap_or_default(),
)?)
} else if ctx.is_canyon_active() { } else if ctx.is_canyon_active() {
Some(EMPTY_WITHDRAWALS) Some(EMPTY_WITHDRAWALS)
} else { } else {

View File

@ -11,10 +11,29 @@ repository.workspace = true
workspace = true workspace = true
[dependencies] [dependencies]
# reth
reth-primitives.workspace = true reth-primitives.workspace = true
reth-trie-common.workspace = true
reth-storage-api.workspace = true
# ethereum
alloy-primitives.workspace = true
revm.workspace = true
[dev-dependencies] [dev-dependencies]
reth-codecs.workspace = true reth-codecs.workspace = true
reth-db-api.workspace = true reth-db-api.workspace = true
reth-prune-types.workspace = true reth-prune-types.workspace = true
reth-stages-types.workspace = true reth-stages-types.workspace = true
[features]
default = ["std"]
std = [
"reth-primitives/std",
"reth-trie-common/std",
"reth-storage-api/std",
"alloy-primitives/std",
"revm/std",
"reth-prune-types/std",
"reth-stages-types/std",
]

View File

@ -6,6 +6,10 @@
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)] )]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
pub mod predeploys;
pub use predeploys::withdrawals_root;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -0,0 +1,29 @@
//! Utilities for accessing Optimism predeploy state
use alloy_primitives::{address, Address, B256};
use reth_storage_api::{errors::ProviderResult, StorageRootProvider};
use reth_trie_common::HashedStorage;
use revm::database::BundleState;
/// The L2 contract `L2ToL1MessagePasser`, stores commitments to withdrawal transactions.
pub const ADDRESS_L2_TO_L1_MESSAGE_PASSER: Address =
address!("4200000000000000000000000000000000000016");
/// Computes the storage root of predeploy `L2ToL1MessagePasser.sol` with state updates from block
/// execution.
pub fn withdrawals_root<DB: StorageRootProvider>(
state_updates: &BundleState,
state: DB,
) -> ProviderResult<B256> {
// if l2 withdrawals transactions were executed, use predeploy storage updates in storage root
// computation
let hashed_storage_updates =
state_updates.state().get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER).map(|acc| {
HashedStorage::from_plain_storage(
acc.status,
acc.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
)
});
state.storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, hashed_storage_updates.unwrap_or_default())
}

View File

@ -19,6 +19,7 @@ pub mod lockfile;
/// Provider error /// Provider error
pub mod provider; pub mod provider;
pub use provider::{ProviderError, ProviderResult};
/// Writer error /// Writer error
pub mod writer; pub mod writer;