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