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}