Skip to main content

reth_revm/
witness.rs

1use alloc::vec::Vec;
2use alloy_primitives::{keccak256, Bytes, B256};
3use reth_trie::{ExecutionWitnessMode, HashedPostState};
4use revm::database::State;
5
6/// Borrows finalized execution state for witness generation.
7#[derive(Debug, Clone)]
8pub struct ExecutionWitnessRecord<'a, DB> {
9    /// State after execution.
10    state: &'a State<DB>,
11    /// Additional hashed state to include in the witness.
12    additional_state: Option<HashedPostState>,
13}
14
15impl<'a, DB> ExecutionWitnessRecord<'a, DB> {
16    /// Creates a new record from the state after execution.
17    pub const fn new(state: &'a State<DB>) -> Self {
18        Self { state, additional_state: None }
19    }
20
21    /// Adds hashed state that should be included when generating the witness.
22    ///
23    /// State recorded during execution takes precedence over additional state for overlapping
24    /// accounts and storage slots.
25    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    /// Converts this record into a complete [`alloy_rpc_types_debug::ExecutionWitness`] by
31    /// generating state proofs and fetching ancestor block headers.
32    ///
33    /// The `block_number` is the number of the block being witnessed. Ancestor headers are
34    /// included based on the lowest block number referenced by BLOCKHASH opcodes during
35    /// execution, or just the parent header if BLOCKHASH was not called.
36    #[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    /// Generates state proofs and codes without fetching ancestor headers.
71    ///
72    /// Callers witnessing non-canonical blocks can supply headers by following parent hashes.
73    #[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                    // cache state does not have all the contracts, especially when
93                    // a contract is created within the block
94                    // the contract only exists in bundle state, therefore we need
95                    // to include them as well
96                    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        // The execution cache does not contain untouched slots of a destroyed account. The
151        // provider expands them into explicit zero writes from the parent state; extending it last
152        // also ensures the bundle's final values override those collected from the cache.
153        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}