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}