1use alloc::vec::Vec;
2use alloy_primitives::{keccak256, Bytes, B256};
3use alloy_rpc_types_debug::ExecutionWitness;
4use evm2::evm::CacheDB;
5use reth_execution_types::BundleSource;
6use reth_primitives_traits::Account as PrimitiveAccount;
7use reth_storage_api::{
8 AccountReader, HashedPostStateProvider, HeaderProvider, StateProofProvider,
9};
10use reth_storage_errors::provider::ProviderResult;
11use reth_trie_common::{bundle_state::BundleState, ExecutionWitnessMode, HashedPostState};
12
13#[derive(Debug, Clone)]
15pub struct ExecutionWitnessRecord<'a, DB> {
16 state: &'a CacheDB<DB>,
18 additional_state: Option<HashedPostState>,
20 canonical_codes: Option<Vec<Bytes>>,
22}
23
24impl<'a, DB> ExecutionWitnessRecord<'a, DB> {
25 pub const fn new(state: &'a CacheDB<DB>) -> Self {
27 Self { state, additional_state: None, canonical_codes: None }
28 }
29
30 pub fn with_additional_state(mut self, additional_state: HashedPostState) -> Self {
35 self.additional_state.get_or_insert_default().extend(additional_state);
36 self
37 }
38
39 pub fn into_execution_witness<SP, HP>(
46 self,
47 state_provider: &SP,
48 headers_provider: &HP,
49 block_number: u64,
50 mode: ExecutionWitnessMode,
51 ) -> ProviderResult<ExecutionWitness>
52 where
53 SP: StateProofProvider + HashedPostStateProvider + AccountReader + ?Sized,
54 HP: HeaderProvider + ?Sized,
55 HP::Header: alloy_rlp::Encodable,
56 {
57 let lowest_block_number =
58 self.state.cache.block_hashes.keys().map(|number| number.saturating_to::<u64>()).min();
59 let mut exec_witness = self.into_execution_witness_without_headers(state_provider, mode)?;
60
61 let smallest = lowest_block_number.unwrap_or_else(|| block_number.saturating_sub(1));
62 exec_witness.headers = headers_provider
63 .headers_range(smallest..block_number)?
64 .into_iter()
65 .map(|header| {
66 let mut buf = Vec::new();
67 alloy_rlp::Encodable::encode(&header, &mut buf);
68 buf.into()
69 })
70 .collect();
71
72 Ok(exec_witness)
73 }
74
75 pub fn into_execution_witness_without_headers<SP>(
79 mut self,
80 state_provider: &SP,
81 mode: ExecutionWitnessMode,
82 ) -> ProviderResult<ExecutionWitness>
83 where
84 SP: StateProofProvider + HashedPostStateProvider + AccountReader + ?Sized,
85 {
86 let codes = if mode.is_canonical() {
87 self.canonical_codes.take().unwrap_or_else(|| Self::capture_codes(self.state))
88 } else {
89 self.state
90 .cache
91 .contracts
92 .iter()
93 .filter(|(hash, _)| !hash.is_zero())
94 .map(|(_, code)| code.original_bytes())
95 .collect()
96 };
97
98 let mut wiped_state = reth_trie_common::bundle_state::BundleState::default();
99 for (address, storage) in &self.state.cache.storage {
100 if storage.wiped {
101 let info =
102 self.state.cache.accounts.get(address).and_then(|info| info.as_ref()).cloned();
103 wiped_state.state.insert(
104 *address,
105 reth_trie_common::bundle_state::BundleAccount::new(
106 state_provider.basic_account(address)?.map(Into::into),
108 info.clone(),
109 Default::default(),
110 if info.is_some() {
111 reth_trie_common::bundle_state::AccountStatus::DestroyedChanged
112 } else {
113 reth_trie_common::bundle_state::AccountStatus::Destroyed
114 },
115 ),
116 );
117 }
118 }
119 if !wiped_state.is_empty() {
120 self.additional_state
121 .get_or_insert_default()
122 .extend(state_provider.hashed_post_state(&wiped_state)?);
123 }
124 let (hashed_state, keys) = self.hashed_post_state();
125 let state = state_provider.witness(Default::default(), hashed_state, mode)?;
126 Ok(ExecutionWitness { state, codes, keys, ..Default::default() })
127 }
128
129 pub fn capture_codes(state: &CacheDB<DB>) -> Vec<Bytes> {
133 let mut codes = state
134 .cache
135 .contracts
136 .iter()
137 .filter(|(hash, code)| !hash.is_zero() && !code.is_empty())
138 .map(|(_, code)| code.original_bytes())
139 .collect::<Vec<_>>();
140 codes.sort_unstable();
141 codes
142 }
143
144 pub fn commit_execution(state: &mut CacheDB<DB>, output: &BundleState) -> Vec<Bytes> {
150 let codes = Self::capture_codes(state);
151 state.commit_source(&BundleSource(output));
152 codes
153 }
154
155 pub fn with_canonical_codes(mut self, codes: Vec<Bytes>) -> Self {
158 self.canonical_codes = Some(codes);
159 self
160 }
161
162 fn hashed_post_state(self) -> (HashedPostState, Vec<Bytes>) {
163 let mut hashed_state = self.additional_state.unwrap_or_default();
164 let mut keys = Vec::new();
165 for (address, account) in &self.state.cache.accounts {
166 let hashed_address = keccak256(address);
167 hashed_state.accounts.insert(
168 hashed_address,
169 account.as_ref().map(|account| PrimitiveAccount {
170 nonce: account.nonce,
171 balance: account.balance,
172 bytecode_hash: (!account.code_hash.is_zero() && !account.is_empty_code_hash())
173 .then_some(account.code_hash),
174 #[cfg(feature = "account-ext")]
175 extension: reth_primitives_traits::AccountExtension::from_shared(
176 account.extension.clone().into_shared(),
177 ),
178 }),
179 );
180 if account.is_some() {
181 keys.push(address.to_vec().into());
182 }
183
184 if let Some(storage) = self.state.cache.storage.get(address) {
185 let hashed_storage = hashed_state.storages.entry(hashed_address).or_default();
186 for (slot, value) in &storage.slots {
187 let slot = B256::from(*slot);
188 hashed_storage.storage.insert(keccak256(slot), *value);
189 keys.push(slot.into());
190 }
191 }
192 }
193 (hashed_state, keys)
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use alloy_primitives::{Address, U256};
201 use evm2::evm::{AccountInfo, EmptyDB};
202 use reth_trie_common::HashedStorage;
203
204 #[test]
205 fn destroyed_account_is_recorded_for_witness() {
206 let address = Address::with_last_byte(1);
207 let hashed_address = keccak256(address);
208
209 let mut state = CacheDB::<EmptyDB>::default();
210 state.cache.accounts.insert(address, None);
211 state.cache.storage.entry(address).or_default().wipe();
212
213 let (hashed_state, _) = ExecutionWitnessRecord::new(&state).hashed_post_state();
214 assert_eq!(hashed_state.accounts[&hashed_address], None);
215 assert!(hashed_state.storages[&hashed_address].storage.is_empty());
216 }
217
218 #[cfg(feature = "account-ext")]
219 #[test]
220 fn witness_preserves_account_extension() {
221 let address = Address::with_last_byte(1);
222 let mut state = CacheDB::<EmptyDB>::default();
223 state.insert_account_info(
224 &address,
225 AccountInfo {
226 extension: evm2::evm::AccountExtension::copy_from_slice(b"account extension"),
227 ..Default::default()
228 },
229 );
230 let (hashed, _) = ExecutionWitnessRecord::new(&state).hashed_post_state();
231 assert_eq!(
232 hashed.accounts[&keccak256(address)].as_ref().unwrap().extension.as_ref(),
233 b"account extension"
234 );
235 }
236
237 #[test]
238 fn additional_state_is_merged_with_executed_state() {
239 let address = Address::with_last_byte(1);
240 let hashed_address = keccak256(address);
241 let slot = U256::from(1);
242 let additional_slot = B256::with_last_byte(2);
243
244 let mut state = CacheDB::<EmptyDB>::default();
245 state.insert_account_info(&address, AccountInfo::default());
246 state.insert_account_storage(&address, &slot, &U256::from(2));
247
248 let additional_state = HashedPostState::default().with_storages([(
249 hashed_address,
250 HashedStorage::from_iter([
251 (keccak256(B256::from(slot)), U256::from(1)),
252 (additional_slot, U256::from(3)),
253 ]),
254 )]);
255
256 let (hashed_state, _) = ExecutionWitnessRecord::new(&state)
257 .with_additional_state(additional_state)
258 .hashed_post_state();
259 let storage = &hashed_state.storages[&hashed_address].storage;
260 assert_eq!(storage[&keccak256(B256::from(slot))], U256::from(2));
261 assert_eq!(storage[&additional_slot], U256::from(3));
262 }
263}