1use alloc::vec::Vec;
2use alloy_primitives::{keccak256, Bytes, B256};
3use reth_trie::{ExecutionWitnessMode, HashedPostState};
4use revm::database::State;
5
6#[derive(Debug, Clone)]
8pub struct ExecutionWitnessRecord<'a, DB> {
9 state: &'a State<DB>,
11 additional_state: Option<HashedPostState>,
13}
14
15impl<'a, DB> ExecutionWitnessRecord<'a, DB> {
16 pub const fn new(state: &'a State<DB>) -> Self {
18 Self { state, additional_state: None }
19 }
20
21 pub fn with_additional_state(mut self, additional_state: HashedPostState) -> Self {
26 self.additional_state.get_or_insert_default().extend(additional_state);
27 self
28 }
29
30 #[cfg(feature = "witness")]
37 pub fn into_execution_witness<SP, HP>(
38 self,
39 state_provider: &SP,
40 headers_provider: &HP,
41 block_number: u64,
42 mode: ExecutionWitnessMode,
43 ) -> reth_storage_errors::provider::ProviderResult<alloy_rpc_types_debug::ExecutionWitness>
44 where
45 SP: reth_storage_api::HashedPostStateProvider
46 + reth_storage_api::StateProofProvider
47 + ?Sized,
48 HP: reth_storage_api::HeaderProvider + ?Sized,
49 HP::Header: alloy_rlp::Encodable,
50 {
51 let lowest_block_number = self.state.block_hashes.lowest().map(|(number, _)| number);
52 let mut exec_witness = self.into_execution_witness_without_headers(state_provider, mode)?;
53
54 let smallest = lowest_block_number.unwrap_or_else(|| block_number.saturating_sub(1));
55 let range = smallest..block_number;
56
57 exec_witness.headers = headers_provider
58 .headers_range(range)?
59 .into_iter()
60 .map(|header| {
61 let mut buf = Vec::new();
62 alloy_rlp::Encodable::encode(&header, &mut buf);
63 buf.into()
64 })
65 .collect();
66
67 Ok(exec_witness)
68 }
69
70 #[cfg(feature = "witness")]
74 pub fn into_execution_witness_without_headers<SP>(
75 self,
76 state_provider: &SP,
77 mode: ExecutionWitnessMode,
78 ) -> reth_storage_errors::provider::ProviderResult<alloy_rpc_types_debug::ExecutionWitness>
79 where
80 SP: reth_storage_api::HashedPostStateProvider
81 + reth_storage_api::StateProofProvider
82 + ?Sized,
83 {
84 let codes = match mode {
85 ExecutionWitnessMode::Legacy => self
86 .state
87 .cache
88 .contracts
89 .values()
90 .map(|code| code.original_bytes())
91 .chain(
92 self.state.bundle_state.contracts.values().map(|code| code.original_bytes()),
97 )
98 .collect(),
99 ExecutionWitnessMode::Canonical => {
100 let mut codes: Vec<_> = self
101 .state
102 .cache
103 .contracts
104 .values()
105 .map(|c| c.original_bytes())
106 .filter(|code| !code.is_empty())
107 .collect();
108 codes.sort_unstable();
109 codes
110 }
111 };
112
113 let (hashed_state, keys) = self.hashed_post_state(state_provider)?;
114
115 let state = state_provider.witness(Default::default(), hashed_state, mode)?;
116 Ok(alloy_rpc_types_debug::ExecutionWitness { state, codes, keys, ..Default::default() })
117 }
118
119 #[cfg(feature = "witness")]
120 fn hashed_post_state<SP>(
121 self,
122 state_provider: &SP,
123 ) -> reth_storage_errors::provider::ProviderResult<(HashedPostState, Vec<Bytes>)>
124 where
125 SP: reth_storage_api::HashedPostStateProvider + ?Sized,
126 {
127 let mut hashed_state = self.additional_state.unwrap_or_default();
128 let mut keys = Vec::new();
129 for (address, account) in &self.state.cache.accounts {
130 let hashed_address = keccak256(address);
131 hashed_state
132 .accounts
133 .insert(hashed_address, account.account.as_ref().map(|a| (&a.info).into()));
134
135 let storage = hashed_state.storages.entry(hashed_address).or_default();
136
137 if let Some(account) = &account.account {
138 keys.push(address.to_vec().into());
139
140 for (slot, value) in &account.storage {
141 let slot = B256::from(*slot);
142 let hashed_slot = keccak256(slot);
143 storage.storage.insert(hashed_slot, *value);
144
145 keys.push(slot.into());
146 }
147 }
148 }
149
150 hashed_state.extend(state_provider.hashed_post_state(&self.state.bundle_state)?);
154 Ok((hashed_state, keys))
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use alloy_primitives::{Address, U256};
162 use reth_storage_api::HashedPostStateProvider;
163 use reth_storage_errors::provider::ProviderResult;
164 use reth_trie::HashedStorage;
165 use revm::{
166 database::{states::CacheAccount, AccountStatus, BundleAccount, EmptyDB},
167 state::AccountInfo,
168 };
169
170 #[derive(Debug)]
171 struct ExpandedStateProvider(HashedPostState);
172
173 impl HashedPostStateProvider for ExpandedStateProvider {
174 fn hashed_post_state(
175 &self,
176 bundle_state: &revm::database::BundleState,
177 ) -> ProviderResult<HashedPostState> {
178 assert!(bundle_state.state.values().any(BundleAccount::was_destroyed));
179 Ok(self.0.clone())
180 }
181 }
182
183 #[derive(Debug)]
184 struct StaticStateProvider(HashedPostState);
185
186 impl HashedPostStateProvider for StaticStateProvider {
187 fn hashed_post_state(
188 &self,
189 _bundle_state: &revm::database::BundleState,
190 ) -> ProviderResult<HashedPostState> {
191 Ok(self.0.clone())
192 }
193 }
194
195 #[test]
196 fn destroyed_account_storage_is_zero_expanded() {
197 let address = Address::with_last_byte(1);
198 let hashed_address = keccak256(address);
199 let hashed_slot = B256::with_last_byte(2);
200
201 let mut state = State::builder().with_database(EmptyDB::default()).build();
202 state.cache.accounts.insert(address, CacheAccount::new_destroyed());
203 state.bundle_state.state.insert(
204 address,
205 BundleAccount::new(
206 Some(AccountInfo::default()),
207 None,
208 Default::default(),
209 AccountStatus::Destroyed,
210 ),
211 );
212
213 let provider = ExpandedStateProvider(
214 HashedPostState::default().with_accounts([(hashed_address, None)]).with_storages([(
215 hashed_address,
216 HashedStorage::from_iter([(hashed_slot, U256::ZERO)]),
217 )]),
218 );
219
220 let (hashed_state, _) =
221 ExecutionWitnessRecord::new(&state).hashed_post_state(&provider).unwrap();
222 let storage = hashed_state.storages.get(&hashed_address).unwrap();
223 assert_eq!(storage.storage.get(&hashed_slot), Some(&U256::ZERO));
224 }
225
226 #[test]
227 fn additional_state_is_merged_with_executed_state() {
228 let address = Address::with_last_byte(1);
229 let hashed_address = keccak256(address);
230 let slot = U256::from(1);
231 let additional_slot = B256::with_last_byte(2);
232
233 let mut state = State::builder().with_database(EmptyDB::default()).build();
234 let account = CacheAccount::new_loaded(
235 AccountInfo::default(),
236 core::iter::once((slot, U256::from(2))).collect(),
237 );
238 state.cache.accounts.insert(address, account);
239
240 let additional_state = HashedPostState::default().with_storages([(
241 hashed_address,
242 HashedStorage::from_iter([
243 (keccak256(B256::from(slot)), U256::from(1)),
244 (additional_slot, U256::from(3)),
245 ]),
246 )]);
247 let provider = StaticStateProvider(HashedPostState::default());
248
249 let (hashed_state, _) = ExecutionWitnessRecord::new(&state)
250 .with_additional_state(additional_state)
251 .hashed_post_state(&provider)
252 .unwrap();
253 let storage = &hashed_state.storages[&hashed_address].storage;
254 assert_eq!(storage[&keccak256(B256::from(slot))], U256::from(2));
255 assert_eq!(storage[&additional_slot], U256::from(3));
256 }
257}