Skip to main content

reth_revm/
witness.rs

1use alloc::vec::Vec;
2use alloy_primitives::{keccak256, Bytes, B256};
3use reth_trie::{ExecutionWitnessMode, HashedPostState, HashedStorage};
4use revm::database::State;
5
6/// Tracks state changes during execution.
7#[derive(Debug, Clone, Default)]
8pub struct ExecutionWitnessRecord {
9    /// Records all state changes
10    pub hashed_state: HashedPostState,
11    /// Map of all contract codes (created / accessed) to their preimages that were required during
12    /// the execution of the block, including during state root recomputation.
13    ///
14    /// `keccak(bytecodes) => bytecodes`
15    pub codes: Vec<Bytes>,
16    /// Map of all hashed account and storage keys (addresses and slots) to their preimages
17    /// (unhashed account addresses and storage slots, respectively) that were required during
18    /// the execution of the block.
19    ///
20    /// `keccak(address|slot) => address|slot`
21    pub keys: Vec<Bytes>,
22    /// The lowest block number referenced by any BLOCKHASH opcode call during transaction
23    /// execution.
24    ///
25    /// This helps determine which ancestor block headers must be included in the
26    /// `ExecutionWitness`.
27    ///
28    /// `None` - when the BLOCKHASH opcode was not called during execution
29    pub lowest_block_number: Option<u64>,
30}
31
32impl ExecutionWitnessRecord {
33    /// Records the state after execution using the given witness generation mode.
34    pub fn record_executed_state<DB>(&mut self, statedb: &State<DB>, mode: ExecutionWitnessMode) {
35        self.codes = match mode {
36            ExecutionWitnessMode::Legacy => statedb
37                .cache
38                .contracts
39                .values()
40                .map(|code| code.original_bytes())
41                .chain(
42                    // cache state does not have all the contracts, especially when
43                    // a contract is created within the block
44                    // the contract only exists in bundle state, therefore we need
45                    // to include them as well
46                    statedb.bundle_state.contracts.values().map(|code| code.original_bytes()),
47                )
48                .collect(),
49            ExecutionWitnessMode::Canonical => {
50                let mut codes: Vec<_> = statedb
51                    .cache
52                    .contracts
53                    .values()
54                    .map(|c| c.original_bytes())
55                    .filter(|code| !code.is_empty())
56                    .collect();
57                codes.sort_unstable();
58                codes
59            }
60        };
61
62        for (address, account) in &statedb.cache.accounts {
63            let hashed_address = keccak256(address);
64            self.hashed_state
65                .accounts
66                .insert(hashed_address, account.account.as_ref().map(|a| (&a.info).into()));
67
68            let storage = self
69                .hashed_state
70                .storages
71                .entry(hashed_address)
72                .or_insert_with(|| HashedStorage::new(account.status.was_destroyed()));
73
74            if let Some(account) = &account.account {
75                self.keys.push(address.to_vec().into());
76
77                for (slot, value) in &account.storage {
78                    let slot = B256::from(*slot);
79                    let hashed_slot = keccak256(slot);
80                    storage.storage.insert(hashed_slot, *value);
81
82                    self.keys.push(slot.into());
83                }
84            }
85        }
86        self.lowest_block_number =
87            statedb.block_hashes.lowest().map(|(block_number, _)| block_number)
88    }
89
90    /// Creates the record from the state after execution.
91    pub fn from_executed_state<DB>(state: &State<DB>, mode: ExecutionWitnessMode) -> Self {
92        let mut record = Self::default();
93        record.record_executed_state(state, mode);
94        record
95    }
96
97    /// Converts this record into a complete [`alloy_rpc_types_debug::ExecutionWitness`] by
98    /// generating state proofs and fetching ancestor block headers.
99    ///
100    /// The `block_number` is the number of the block being witnessed. Ancestor headers are
101    /// included based on the lowest block number referenced by BLOCKHASH opcodes during
102    /// execution, or just the parent header if BLOCKHASH was not called.
103    #[cfg(feature = "witness")]
104    pub fn into_execution_witness<SP, HP>(
105        self,
106        state_provider: &SP,
107        headers_provider: &HP,
108        block_number: u64,
109        mode: ExecutionWitnessMode,
110    ) -> reth_storage_errors::provider::ProviderResult<alloy_rpc_types_debug::ExecutionWitness>
111    where
112        SP: reth_storage_api::StateProofProvider + ?Sized,
113        HP: reth_storage_api::HeaderProvider + ?Sized,
114        HP::Header: alloy_rlp::Encodable,
115    {
116        let Self { hashed_state, codes, keys, lowest_block_number } = self;
117
118        let state = state_provider.witness(Default::default(), hashed_state, mode)?;
119        let mut exec_witness =
120            alloy_rpc_types_debug::ExecutionWitness { state, codes, keys, ..Default::default() };
121
122        let smallest = lowest_block_number.unwrap_or_else(|| block_number.saturating_sub(1));
123        let range = smallest..block_number;
124
125        exec_witness.headers = headers_provider
126            .headers_range(range)?
127            .into_iter()
128            .map(|header| {
129                let mut buf = Vec::new();
130                alloy_rlp::Encodable::encode(&header, &mut buf);
131                buf.into()
132            })
133            .collect();
134
135        Ok(exec_witness)
136    }
137}