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

View File

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

View File

@ -3,10 +3,9 @@
use crate::OpConsensusError;
use alloy_consensus::BlockHeader;
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_trie_common::HashedStorage;
use revm::database::BundleAccount;
use revm::database::BundleState;
/// Verifies that `withdrawals_root` (i.e. `l2tol1-msg-passer` storage root since Isthmus) field is
/// 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>.
pub fn verify_withdrawals_storage_root<DB, H>(
predeploy_account_updates: Option<&BundleAccount>,
state_updates: &BundleState,
state: DB,
header: H,
) -> Result<(), OpConsensusError>
@ -34,17 +33,7 @@ where
let header_storage_root =
header.withdrawals_root().ok_or(OpConsensusError::L2WithdrawalsRootMissing)?;
// if block contained l2 withdrawals transactions, use predeploy storage updates from
// 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())
let storage_root = withdrawals_root(state_updates, state)
.map_err(OpConsensusError::L2WithdrawalsRootCalculationFail)?;
if header_storage_root != storage_root {
@ -68,12 +57,13 @@ mod test {
use reth_db_common::init::init_genesis;
use reth_optimism_chainspec::OpChainSpecBuilder;
use reth_optimism_node::OpNode;
use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
use reth_provider::{
providers::BlockchainProvider, test_utils::create_test_provider_factory_with_node_types,
StateWriter,
};
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;
#[test]
@ -118,7 +108,7 @@ mod test {
// validate block against existing state by passing empty state updates
verify_withdrawals_storage_root(
None,
&BundleState::default(),
state_provider_factory.latest().expect("load state"),
&header,
)

View File

@ -27,13 +27,13 @@ reth-payload-util.workspace = true
reth-payload-primitives = { workspace = true, features = ["op"] }
reth-basic-payload-builder.workspace = true
reth-chain-state.workspace = true
reth-trie.workspace = true
# op-reth
reth-optimism-consensus.workspace = true
reth-optimism-evm.workspace = true
reth-optimism-forks.workspace = true
reth-optimism-primitives.workspace = true
reth-optimism-storage.workspace = true
# ethereum
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_evm::{OpReceiptBuilder, ReceiptBuilderCtx};
use reth_optimism_forks::OpHardforks;
use reth_optimism_primitives::{
transaction::signed::OpTransaction, ADDRESS_L2_TO_L1_MESSAGE_PASSER,
};
use reth_optimism_primitives::transaction::signed::OpTransaction;
use reth_optimism_storage::predeploys;
use reth_payload_builder_primitives::PayloadBuilderError;
use reth_payload_primitives::PayloadBuilderAttributes;
use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions};
@ -49,7 +48,6 @@ use reth_revm::{
witness::ExecutionWitnessRecord,
};
use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool};
use reth_trie::HashedStorage;
use revm::{
context_interface::{
result::{ExecutionResult, ResultAndState},
@ -400,22 +398,9 @@ impl<Txs> OpBuilder<'_, Txs> {
state.merge_transitions(BundleRetention::Reverts);
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
// `l2tol1-message-passer`
Some(state.database.as_ref().storage_root(
ADDRESS_L2_TO_L1_MESSAGE_PASSER,
hashed_storage_updates.unwrap_or_default(),
)?)
Some(predeploys::withdrawals_root(&state.bundle_state, state.database.as_ref())?)
} else if ctx.is_canyon_active() {
Some(EMPTY_WITHDRAWALS)
} else {

View File

@ -11,10 +11,29 @@ repository.workspace = true
workspace = true
[dependencies]
# reth
reth-primitives.workspace = true
reth-trie-common.workspace = true
reth-storage-api.workspace = true
# ethereum
alloy-primitives.workspace = true
revm.workspace = true
[dev-dependencies]
reth-codecs.workspace = true
reth-db-api.workspace = true
reth-prune-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/"
)]
#![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)]
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
pub mod provider;
pub use provider::{ProviderError, ProviderResult};
/// Writer error
pub mod writer;