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