1use alloc::vec::Vec;
2use alloy_primitives::{keccak256, Bytes, B256};
3use reth_trie::{ExecutionWitnessMode, HashedPostState, HashedStorage};
4use revm::database::State;
5
6#[derive(Debug, Clone, Copy)]
8pub struct ExecutionWitnessRecord<'a, DB> {
9 state: &'a State<DB>,
11}
12
13impl<'a, DB> ExecutionWitnessRecord<'a, DB> {
14 pub const fn new(state: &'a State<DB>) -> Self {
16 Self { state }
17 }
18
19 #[cfg(feature = "witness")]
26 pub fn into_execution_witness<SP, HP>(
27 self,
28 state_provider: &SP,
29 headers_provider: &HP,
30 block_number: u64,
31 mode: ExecutionWitnessMode,
32 ) -> reth_storage_errors::provider::ProviderResult<alloy_rpc_types_debug::ExecutionWitness>
33 where
34 SP: reth_storage_api::HashedPostStateProvider
35 + reth_storage_api::StateProofProvider
36 + ?Sized,
37 HP: reth_storage_api::HeaderProvider + ?Sized,
38 HP::Header: alloy_rlp::Encodable,
39 {
40 let codes = match mode {
41 ExecutionWitnessMode::Legacy => self
42 .state
43 .cache
44 .contracts
45 .values()
46 .map(|code| code.original_bytes())
47 .chain(
48 self.state.bundle_state.contracts.values().map(|code| code.original_bytes()),
53 )
54 .collect(),
55 ExecutionWitnessMode::Canonical => {
56 let mut codes: Vec<_> = self
57 .state
58 .cache
59 .contracts
60 .values()
61 .map(|c| c.original_bytes())
62 .filter(|code| !code.is_empty())
63 .collect();
64 codes.sort_unstable();
65 codes
66 }
67 };
68
69 let (hashed_state, keys) = self.hashed_post_state(state_provider)?;
70
71 let state = state_provider.witness(Default::default(), hashed_state, mode)?;
72 let mut exec_witness =
73 alloy_rpc_types_debug::ExecutionWitness { state, codes, keys, ..Default::default() };
74
75 let lowest_block_number =
76 self.state.block_hashes.lowest().map(|(block_number, _)| block_number);
77 let smallest = lowest_block_number.unwrap_or_else(|| block_number.saturating_sub(1));
78 let range = smallest..block_number;
79
80 exec_witness.headers = headers_provider
81 .headers_range(range)?
82 .into_iter()
83 .map(|header| {
84 let mut buf = Vec::new();
85 alloy_rlp::Encodable::encode(&header, &mut buf);
86 buf.into()
87 })
88 .collect();
89
90 Ok(exec_witness)
91 }
92
93 #[cfg(feature = "witness")]
94 fn hashed_post_state<SP>(
95 &self,
96 state_provider: &SP,
97 ) -> reth_storage_errors::provider::ProviderResult<(HashedPostState, Vec<Bytes>)>
98 where
99 SP: reth_storage_api::HashedPostStateProvider + ?Sized,
100 {
101 let mut hashed_state = HashedPostState::default();
102 let mut keys = Vec::new();
103 for (address, account) in &self.state.cache.accounts {
104 let hashed_address = keccak256(address);
105 hashed_state
106 .accounts
107 .insert(hashed_address, account.account.as_ref().map(|a| (&a.info).into()));
108
109 let storage = hashed_state
110 .storages
111 .entry(hashed_address)
112 .or_insert_with(|| HashedStorage::new(false));
113
114 if let Some(account) = &account.account {
115 keys.push(address.to_vec().into());
116
117 for (slot, value) in &account.storage {
118 let slot = B256::from(*slot);
119 let hashed_slot = keccak256(slot);
120 storage.storage.insert(hashed_slot, *value);
121
122 keys.push(slot.into());
123 }
124 }
125 }
126
127 hashed_state.extend(state_provider.hashed_post_state(&self.state.bundle_state)?);
131 Ok((hashed_state, keys))
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use alloy_primitives::{Address, U256};
139 use reth_storage_api::HashedPostStateProvider;
140 use reth_storage_errors::provider::ProviderResult;
141 use revm::{
142 database::{states::CacheAccount, AccountStatus, BundleAccount, EmptyDB},
143 state::AccountInfo,
144 };
145
146 #[derive(Debug)]
147 struct ExpandedStateProvider(HashedPostState);
148
149 impl HashedPostStateProvider for ExpandedStateProvider {
150 fn hashed_post_state(
151 &self,
152 bundle_state: &revm::database::BundleState,
153 ) -> ProviderResult<HashedPostState> {
154 assert!(bundle_state.state.values().any(BundleAccount::was_destroyed));
155 Ok(self.0.clone())
156 }
157 }
158
159 #[test]
160 fn destroyed_account_storage_is_zero_expanded_without_wipe() {
161 let address = Address::with_last_byte(1);
162 let hashed_address = keccak256(address);
163 let hashed_slot = B256::with_last_byte(2);
164
165 let mut state = State::builder().with_database(EmptyDB::default()).build();
166 state.cache.accounts.insert(address, CacheAccount::new_destroyed());
167 state.bundle_state.state.insert(
168 address,
169 BundleAccount::new(
170 Some(AccountInfo::default()),
171 None,
172 Default::default(),
173 AccountStatus::Destroyed,
174 ),
175 );
176
177 let provider = ExpandedStateProvider(
178 HashedPostState::default().with_accounts([(hashed_address, None)]).with_storages([(
179 hashed_address,
180 HashedStorage::from_iter([(hashed_slot, U256::ZERO)]),
181 )]),
182 );
183
184 let (hashed_state, _) =
185 ExecutionWitnessRecord::new(&state).hashed_post_state(&provider).unwrap();
186 let storage = hashed_state.storages.get(&hashed_address).unwrap();
187 assert!(!storage.wiped);
188 assert_eq!(storage.storage.get(&hashed_slot), Some(&U256::ZERO));
189 }
190}