reth_optimism_consensus/validation/
isthmus.rs1use crate::OpConsensusError;
4use alloy_consensus::BlockHeader;
5use alloy_primitives::{address, Address, B256};
6use alloy_trie::EMPTY_ROOT_HASH;
7use reth_storage_api::{errors::ProviderResult, StorageRootProvider};
8use reth_trie_common::HashedStorage;
9use revm::database::BundleState;
10use tracing::warn;
11
12pub const ADDRESS_L2_TO_L1_MESSAGE_PASSER: Address =
14 address!("0x4200000000000000000000000000000000000016");
15
16pub fn ensure_withdrawals_storage_root_is_some<H: BlockHeader>(
19 header: H,
20) -> Result<(), OpConsensusError> {
21 header.withdrawals_root().ok_or(OpConsensusError::L2WithdrawalsRootMissing)?;
22
23 Ok(())
24}
25
26pub fn withdrawals_root<DB: StorageRootProvider>(
30 state_updates: &BundleState,
31 state: DB,
32) -> ProviderResult<B256> {
33 withdrawals_root_prehashed(
36 state_updates
37 .state()
38 .get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER)
39 .map(|acc| {
40 HashedStorage::from_plain_storage(
41 acc.status,
42 acc.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
43 )
44 })
45 .unwrap_or_default(),
46 state,
47 )
48}
49
50pub fn withdrawals_root_prehashed<DB: StorageRootProvider>(
55 hashed_storage_updates: HashedStorage,
56 state: DB,
57) -> ProviderResult<B256> {
58 state.storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, hashed_storage_updates)
59}
60
61pub fn verify_withdrawals_root<DB, H>(
68 state_updates: &BundleState,
69 state: DB,
70 header: H,
71) -> Result<(), OpConsensusError>
72where
73 DB: StorageRootProvider,
74 H: BlockHeader,
75{
76 let header_storage_root =
77 header.withdrawals_root().ok_or(OpConsensusError::L2WithdrawalsRootMissing)?;
78
79 let storage_root = withdrawals_root(state_updates, state)
80 .map_err(OpConsensusError::L2WithdrawalsRootCalculationFail)?;
81
82 if storage_root == EMPTY_ROOT_HASH {
83 warn!("isthmus: no storage root for L2ToL1MessagePasser contract");
86 }
87
88 if header_storage_root != storage_root {
89 return Err(OpConsensusError::L2WithdrawalsRootMismatch {
90 header: header_storage_root,
91 exec_res: storage_root,
92 })
93 }
94
95 Ok(())
96}
97
98pub fn verify_withdrawals_root_prehashed<DB, H>(
106 hashed_storage_updates: HashedStorage,
107 state: DB,
108 header: H,
109) -> Result<(), OpConsensusError>
110where
111 DB: StorageRootProvider,
112 H: BlockHeader,
113{
114 let header_storage_root =
115 header.withdrawals_root().ok_or(OpConsensusError::L2WithdrawalsRootMissing)?;
116
117 let storage_root = withdrawals_root_prehashed(hashed_storage_updates, state)
118 .map_err(OpConsensusError::L2WithdrawalsRootCalculationFail)?;
119
120 if header_storage_root != storage_root {
121 return Err(OpConsensusError::L2WithdrawalsRootMismatch {
122 header: header_storage_root,
123 exec_res: storage_root,
124 })
125 }
126
127 Ok(())
128}
129
130#[cfg(test)]
131mod test {
132 use super::*;
133 use alloc::sync::Arc;
134 use alloy_chains::Chain;
135 use alloy_consensus::Header;
136 use alloy_primitives::{keccak256, B256, U256};
137 use core::str::FromStr;
138 use reth_db_common::init::init_genesis;
139 use reth_optimism_chainspec::OpChainSpecBuilder;
140 use reth_optimism_node::OpNode;
141 use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
142 use reth_provider::{
143 providers::BlockchainProvider, test_utils::create_test_provider_factory_with_node_types,
144 StateWriter,
145 };
146 use reth_revm::db::BundleState;
147 use reth_storage_api::StateProviderFactory;
148 use reth_trie::{test_utils::storage_root_prehashed, HashedStorage};
149 use reth_trie_common::HashedPostState;
150
151 #[test]
152 fn l2tol1_message_passer_no_withdrawals() {
153 let hashed_address = keccak256(ADDRESS_L2_TO_L1_MESSAGE_PASSER);
154
155 let init_storage = HashedStorage::from_iter(
157 false,
158 [
159 "50000000000000000000000000000004253371b55351a08cb3267d4d265530b6",
160 "512428ed685fff57294d1a9cbb147b18ae5db9cf6ae4b312fa1946ba0561882e",
161 "51e6784c736ef8548f856909870b38e49ef7a4e3e77e5e945e0d5e6fcaa3037f",
162 ]
163 .into_iter()
164 .map(|str| (B256::from_str(str).unwrap(), U256::from(1))),
165 );
166 let mut state = HashedPostState::default();
167 state.storages.insert(hashed_address, init_storage.clone());
168
169 let provider_factory = create_test_provider_factory_with_node_types::<OpNode>(Arc::new(
173 OpChainSpecBuilder::default().chain(Chain::dev()).genesis(Default::default()).build(),
174 ));
175 let _ = init_genesis(&provider_factory).unwrap();
176
177 let provider_rw = provider_factory.provider_rw().unwrap();
179 provider_rw.write_hashed_state(&state.clone().into_sorted()).unwrap();
180 provider_rw.commit().unwrap();
181
182 let header = Header {
184 withdrawals_root: Some(storage_root_prehashed(init_storage.storage)),
185 ..Default::default()
186 };
187
188 let state_provider_factory = BlockchainProvider::new(provider_factory).unwrap();
190
191 verify_withdrawals_root(
193 &BundleState::default(),
194 state_provider_factory.latest().expect("load state"),
195 &header,
196 )
197 .unwrap();
198 }
199}