reth_stateless/
witness_db.rs

1//! Provides the [`WitnessDatabase`] type, an implementation of [`reth_revm::Database`]
2//! specifically designed for stateless execution environments.
3
4use alloc::{collections::btree_map::BTreeMap, format};
5use alloy_primitives::{keccak256, map::B256Map, Address, FlaggedStorage, B256, U256};
6use alloy_rlp::Decodable;
7use alloy_trie::{TrieAccount, EMPTY_ROOT_HASH};
8use reth_errors::ProviderError;
9use reth_revm::{bytecode::Bytecode, state::AccountInfo, Database};
10use reth_trie_sparse::SparseStateTrie;
11
12/// An EVM database implementation backed by witness data.
13///
14/// This struct implements the [`reth_revm::Database`] trait, allowing the EVM to execute
15/// transactions using:
16///  - Account and storage slot data provided by a [`reth_trie_sparse::SparseStateTrie`].
17///  - Bytecode and ancestor block hashes provided by in-memory maps.
18///
19/// This is designed for stateless execution scenarios where direct access to a full node's
20/// database is not available or desired.
21#[derive(Debug)]
22pub(crate) struct WitnessDatabase<'a> {
23    /// Map of block numbers to block hashes.
24    /// This is used to service the `BLOCKHASH` opcode.
25    // TODO: use Vec instead -- ancestors should be contiguous
26    // TODO: so we can use the current_block_number and an offset to
27    // TODO: get the block number of a particular ancestor
28    block_hashes_by_block_number: BTreeMap<u64, B256>,
29    /// Map of code hashes to bytecode.
30    /// Used to fetch contract code needed during execution.
31    bytecode: B256Map<Bytecode>,
32    /// The sparse Merkle Patricia Trie containing account and storage state.
33    /// This is used to provide account/storage values during EVM execution.
34    /// TODO: Ideally we do not have this trie and instead a simple map.
35    /// TODO: Then as a corollary we can avoid unnecessary hashing in `Database::storage`
36    /// TODO: and `Database::basic` without needing to cache the hashed Addresses and Keys
37    trie: &'a SparseStateTrie,
38}
39
40impl<'a> WitnessDatabase<'a> {
41    /// Creates a new [`WitnessDatabase`] instance.
42    ///
43    /// # Assumptions
44    ///
45    /// This function assumes:
46    /// 1. The provided `trie` has been populated with state data consistent with a known state root
47    ///    (e.g., using witness data and verifying against a parent block's state root).
48    /// 2. The `bytecode` map contains all bytecode corresponding to code hashes present in the
49    ///    account data within the `trie`.
50    /// 3. The `ancestor_hashes` map contains the block hashes for the relevant ancestor blocks (up
51    ///    to 256 including the current block number). It assumes these hashes correspond to a
52    ///    contiguous chain of blocks. The caller is responsible for verifying the contiguity and
53    ///    the block limit.
54    pub(crate) const fn new(
55        trie: &'a SparseStateTrie,
56        bytecode: B256Map<Bytecode>,
57        ancestor_hashes: BTreeMap<u64, B256>,
58    ) -> Self {
59        Self { trie, block_hashes_by_block_number: ancestor_hashes, bytecode }
60    }
61}
62
63impl Database for WitnessDatabase<'_> {
64    /// The database error type.
65    type Error = ProviderError;
66
67    /// Get basic account information by hashing the address and looking up the account RLP
68    /// in the underlying [`SparseStateTrie`].
69    ///
70    /// Returns `Ok(None)` if the account is not found in the trie.
71    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
72        let hashed_address = keccak256(address);
73
74        if let Some(bytes) = self.trie.get_account_value(&hashed_address) {
75            let account = TrieAccount::decode(&mut bytes.as_slice())?;
76            return Ok(Some(AccountInfo {
77                balance: account.balance,
78                nonce: account.nonce,
79                code_hash: account.code_hash,
80                code: None,
81            }));
82        }
83
84        if !self.trie.check_valid_account_witness(hashed_address) {
85            return Err(ProviderError::TrieWitnessError(format!(
86                "incomplete account witness for {hashed_address:?}"
87            )));
88        }
89
90        Ok(None)
91    }
92
93    /// Get storage value of an account at a specific slot.
94    ///
95    /// Returns `U256::ZERO` if the slot is not found in the trie.
96    fn storage(&mut self, address: Address, slot: U256) -> Result<FlaggedStorage, Self::Error> {
97        let hashed_address = keccak256(address);
98        let hashed_slot = keccak256(B256::from(slot));
99
100        if let Some(raw) = self.trie.get_storage_slot_value(&hashed_address, &hashed_slot) {
101            return Ok(FlaggedStorage::decode(&mut raw.as_slice())?)
102        }
103
104        // Storage slot value is not present in the trie, validate that the witness is complete.
105        // If the account exists in the trie...
106        if let Some(bytes) = self.trie.get_account_value(&hashed_address) {
107            // ...check that its storage is either empty or the storage trie was sufficiently
108            // revealed...
109            let account = TrieAccount::decode(&mut bytes.as_slice())?;
110            if account.storage_root != EMPTY_ROOT_HASH &&
111                !self.trie.check_valid_storage_witness(hashed_address, hashed_slot)
112            {
113                return Err(ProviderError::TrieWitnessError(format!(
114                    "incomplete storage witness: prover must supply exclusion proof for slot {hashed_slot:?} in account {hashed_address:?}"
115                )));
116            }
117        } else if !self.trie.check_valid_account_witness(hashed_address) {
118            // ...else if account is missing, validate that the account trie was sufficiently
119            // revealed.
120            return Err(ProviderError::TrieWitnessError(format!(
121                "incomplete account witness for {hashed_address:?}"
122            )));
123        }
124
125        Ok(FlaggedStorage::ZERO)
126    }
127
128    /// Get account code by its hash from the provided bytecode map.
129    ///
130    /// Returns an error if the bytecode for the given hash is not found in the map.
131    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
132        let bytecode = self.bytecode.get(&code_hash).ok_or_else(|| {
133            ProviderError::TrieWitnessError(format!("bytecode for {code_hash} not found"))
134        })?;
135
136        Ok(bytecode.clone())
137    }
138
139    /// Get block hash by block number from the provided ancestor hashes map.
140    ///
141    /// Returns an error if the hash for the given block number is not found in the map.
142    fn block_hash(&mut self, block_number: u64) -> Result<B256, Self::Error> {
143        self.block_hashes_by_block_number
144            .get(&block_number)
145            .copied()
146            .ok_or(ProviderError::StateForNumberNotFound(block_number))
147    }
148}