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 crate::trie::StatelessTrie;
5use alloc::{collections::btree_map::BTreeMap, format};
6use alloy_primitives::{map::B256Map, Address, B256, U256};
7use reth_errors::ProviderError;
8use reth_revm::{bytecode::Bytecode, state::AccountInfo, Database};
9
10/// An EVM database implementation backed by witness data.
11///
12/// This struct implements the [`reth_revm::Database`] trait, allowing the EVM to execute
13/// transactions using:
14///  - Account and storage slot data provided by a [`StatelessTrie`] implementation.
15///  - Bytecode and ancestor block hashes provided by in-memory maps.
16///
17/// This is designed for stateless execution scenarios where direct access to a full node's
18/// database is not available or desired.
19#[derive(Debug)]
20pub(crate) struct WitnessDatabase<'a, T>
21where
22    T: StatelessTrie,
23{
24    /// Map of block numbers to block hashes.
25    /// This is used to service the `BLOCKHASH` opcode.
26    // TODO: use Vec instead -- ancestors should be contiguous
27    // TODO: so we can use the current_block_number and an offset to
28    // TODO: get the block number of a particular ancestor
29    block_hashes_by_block_number: BTreeMap<u64, B256>,
30    /// Map of code hashes to bytecode.
31    /// Used to fetch contract code needed during execution.
32    bytecode: B256Map<Bytecode>,
33    /// The sparse Merkle Patricia Trie containing account and storage state.
34    /// This is used to provide account/storage values during EVM execution.
35    /// TODO: Ideally we do not have this trie and instead a simple map.
36    /// TODO: Then as a corollary we can avoid unnecessary hashing in `Database::storage`
37    /// TODO: and `Database::basic` without needing to cache the hashed Addresses and Keys
38    trie: &'a T,
39}
40
41impl<'a, T> WitnessDatabase<'a, T>
42where
43    T: StatelessTrie,
44{
45    /// Creates a new [`WitnessDatabase`] instance.
46    ///
47    /// # Assumptions
48    ///
49    /// This function assumes:
50    /// 1. The provided `trie` has been populated with state data consistent with a known state root
51    ///    (e.g., using witness data and verifying against a parent block's state root).
52    /// 2. The `bytecode` map contains all bytecode corresponding to code hashes present in the
53    ///    account data within the `trie`.
54    /// 3. The `ancestor_hashes` map contains the block hashes for the relevant ancestor blocks (up
55    ///    to 256 including the current block number). It assumes these hashes correspond to a
56    ///    contiguous chain of blocks. The caller is responsible for verifying the contiguity and
57    ///    the block limit.
58    pub(crate) const fn new(
59        trie: &'a T,
60        bytecode: B256Map<Bytecode>,
61        ancestor_hashes: BTreeMap<u64, B256>,
62    ) -> Self {
63        Self { trie, block_hashes_by_block_number: ancestor_hashes, bytecode }
64    }
65}
66
67impl<T> Database for WitnessDatabase<'_, T>
68where
69    T: StatelessTrie,
70{
71    /// The database error type.
72    type Error = ProviderError;
73
74    /// Get basic account information by hashing the address and looking up the account RLP
75    /// in the underlying [`StatelessTrie`] implementation.
76    ///
77    /// Returns `Ok(None)` if the account is not found in the trie.
78    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
79        self.trie.account(address).map(|opt| {
80            opt.map(|account| AccountInfo {
81                balance: account.balance,
82                nonce: account.nonce,
83                code_hash: account.code_hash,
84                code: None,
85            })
86        })
87    }
88
89    /// Get storage value of an account at a specific slot.
90    ///
91    /// Returns `U256::ZERO` if the slot is not found in the trie.
92    fn storage(&mut self, address: Address, slot: U256) -> Result<U256, Self::Error> {
93        self.trie.storage(address, slot)
94    }
95
96    /// Get account code by its hash from the provided bytecode map.
97    ///
98    /// Returns an error if the bytecode for the given hash is not found in the map.
99    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
100        self.bytecode.get(&code_hash).cloned().ok_or_else(|| {
101            ProviderError::TrieWitnessError(format!("bytecode for {code_hash} not found"))
102        })
103    }
104
105    /// Get block hash by block number from the provided ancestor hashes map.
106    ///
107    /// Returns an error if the hash for the given block number is not found in the map.
108    fn block_hash(&mut self, block_number: u64) -> Result<B256, Self::Error> {
109        self.block_hashes_by_block_number
110            .get(&block_number)
111            .copied()
112            .ok_or(ProviderError::StateForNumberNotFound(block_number))
113    }
114}