kona_executor/db/
mod.rs

1//! This module contains an implementation of an in-memory Trie DB for [revm], that allows for
2//! incremental updates through fetching node preimages on the fly during execution.
3
4use crate::errors::{TrieDBError, TrieDBResult};
5use alloc::{string::ToString, vec::Vec};
6use alloy_consensus::{EMPTY_ROOT_HASH, Header, Sealed};
7use alloy_primitives::{Address, B256, U256, keccak256};
8use alloy_rlp::{Decodable, Encodable};
9use alloy_trie::TrieAccount;
10use kona_mpt::{Nibbles, TrieHinter, TrieNode, TrieNodeError};
11use revm::{
12    Database,
13    db::{BundleState, states::StorageSlot},
14    primitives::{AccountInfo, BLOCK_HASH_HISTORY, Bytecode, HashMap},
15};
16
17mod traits;
18pub use traits::{NoopTrieDBProvider, TrieDBProvider};
19
20/// A Trie DB that caches open state in-memory.
21///
22/// When accounts that don't already exist within the cached [TrieNode] are queried, the database
23/// fetches the preimages of the trie nodes on the path to the account using the `PreimageFetcher`
24/// (`F` generic). This allows for data to be fetched in a verifiable manner given an initial
25/// trusted state root as it is needed during execution.
26///
27/// The [TrieDB] is intended to be wrapped by a [State], which is then used by the [revm::Evm] to
28/// capture state transitions during block execution.
29///
30/// **Behavior**:
31/// - When an account is queried and the trie path has not already been opened by [Self::basic], we
32///   fall through to the `PreimageFetcher` to fetch the preimages of the trie nodes on the path to
33///   the account. After it has been fetched, the path will be cached until the next call to
34///   [Self::state_root].
35/// - When querying for the code hash of an account, the `CodeHashFetcher` is consulted to fetch the
36///   code hash of the account.
37/// - When a [BundleState] changeset is committed to the parent [State] database, the changes are
38///   first applied to the [State]'s cache, then the trie hash is recomputed with
39///   [Self::state_root].
40/// - When the block hash of a block number is needed via [Self::block_hash], the
41///   `HeaderByHashFetcher` is consulted to walk back to the desired block number by revealing the
42///   parent hash of block headers until the desired block number is reached, up to a maximum of
43///   [BLOCK_HASH_HISTORY] blocks back relative to the current parent block hash.
44///
45/// **Example Construction**:
46/// ```rust
47/// use alloy_consensus::{Header, Sealable};
48/// use alloy_primitives::{B256, Bytes};
49/// use kona_executor::{NoopTrieDBProvider, TrieDB};
50/// use kona_mpt::NoopTrieHinter;
51/// use revm::{EvmBuilder, StateBuilder, db::states::bundle_state::BundleRetention};
52///
53/// let mock_starting_root = B256::default();
54/// let mock_parent_block_header = Header::default();
55///
56/// let trie_db = TrieDB::new(
57///     mock_starting_root,
58///     mock_parent_block_header.seal_slow(),
59///     NoopTrieDBProvider,
60///     NoopTrieHinter,
61/// );
62/// let mut state = StateBuilder::new_with_database(trie_db).with_bundle_update().build();
63/// let evm = EvmBuilder::default().with_db(&mut state).build();
64///
65/// // Execute your block's transactions...
66///
67/// // Drop the EVM prior to merging the state transitions.
68/// drop(evm);
69///
70/// state.merge_transitions(BundleRetention::Reverts);
71/// let bundle = state.take_bundle();
72/// let state_root = state.database.state_root(&bundle).expect("Failed to compute state root");
73/// ```
74///
75/// [State]: revm::State
76#[derive(Debug, Clone)]
77pub struct TrieDB<F, H>
78where
79    F: TrieDBProvider,
80    H: TrieHinter,
81{
82    /// The [TrieNode] representation of the root node.
83    root_node: TrieNode,
84    /// Storage roots of accounts within the trie.
85    storage_roots: HashMap<Address, TrieNode>,
86    /// The parent block hash of the current block.
87    parent_block_header: Sealed<Header>,
88    /// The [TrieDBProvider]
89    pub fetcher: F,
90    /// The [TrieHinter]
91    pub hinter: H,
92}
93
94impl<F, H> TrieDB<F, H>
95where
96    F: TrieDBProvider,
97    H: TrieHinter,
98{
99    /// Creates a new [TrieDB] with the given root node.
100    pub fn new(root: B256, parent_block_header: Sealed<Header>, fetcher: F, hinter: H) -> Self {
101        Self {
102            root_node: TrieNode::new_blinded(root),
103            storage_roots: Default::default(),
104            parent_block_header,
105            fetcher,
106            hinter,
107        }
108    }
109
110    /// Consumes `Self` and takes the current state root of the trie DB.
111    pub fn take_root_node(self) -> TrieNode {
112        self.root_node
113    }
114
115    /// Returns a shared reference to the root [TrieNode] of the trie DB.
116    pub const fn root(&self) -> &TrieNode {
117        &self.root_node
118    }
119
120    /// Returns the mapping of [Address]es to storage roots.
121    pub const fn storage_roots(&self) -> &HashMap<Address, TrieNode> {
122        &self.storage_roots
123    }
124
125    /// Returns a reference to the current parent block header of the trie DB.
126    pub const fn parent_block_header(&self) -> &Sealed<Header> {
127        &self.parent_block_header
128    }
129
130    /// Sets the parent block header of the trie DB. Should be called after a block has been
131    /// executed and the Header has been created.
132    ///
133    /// ## Takes
134    /// - `parent_block_header`: The parent block header of the current block.
135    pub fn set_parent_block_header(&mut self, parent_block_header: Sealed<Header>) {
136        self.parent_block_header = parent_block_header;
137    }
138
139    /// Applies a [BundleState] changeset to the [TrieNode] and recomputes the state root hash.
140    ///
141    /// ## Takes
142    /// - `bundle`: The [BundleState] changeset to apply to the trie DB.
143    ///
144    /// ## Returns
145    /// - `Ok(B256)`: The new state root hash of the trie DB.
146    /// - `Err(_)`: If the state root hash could not be computed.
147    pub fn state_root(&mut self, bundle: &BundleState) -> TrieDBResult<B256> {
148        debug!(target: "client_executor", "Recomputing state root");
149
150        // Update the accounts in the trie with the changeset.
151        self.update_accounts(bundle)?;
152
153        // Recompute the root hash of the trie.
154        let root = self.root_node.blind();
155
156        debug!(
157            target: "client_executor",
158            "Recomputed state root: {root}",
159        );
160
161        // Extract the new state root from the root node.
162        Ok(root)
163    }
164
165    /// Fetches the [TrieAccount] of an account from the trie DB.
166    ///
167    /// ## Takes
168    /// - `address`: The address of the account.
169    ///
170    /// ## Returns
171    /// - `Ok(Some(TrieAccount))`: The [TrieAccount] of the account.
172    /// - `Ok(None)`: If the account does not exist in the trie.
173    /// - `Err(_)`: If the account could not be fetched.
174    pub fn get_trie_account(
175        &mut self,
176        address: &Address,
177        block_number: u64,
178    ) -> TrieDBResult<Option<TrieAccount>> {
179        // Send a hint to the host to fetch the account proof.
180        self.hinter
181            .hint_account_proof(*address, block_number)
182            .map_err(|e| TrieDBError::Provider(e.to_string()))?;
183
184        // Fetch the account from the trie.
185        let hashed_address_nibbles = Nibbles::unpack(keccak256(address.as_slice()));
186        let Some(trie_account_rlp) = self.root_node.open(&hashed_address_nibbles, &self.fetcher)?
187        else {
188            return Ok(None);
189        };
190
191        // Decode the trie account from the RLP bytes.
192        TrieAccount::decode(&mut trie_account_rlp.as_ref())
193            .map_err(TrieNodeError::RLPError)
194            .map_err(Into::into)
195            .map(Some)
196    }
197
198    /// Modifies the accounts in the storage trie with the given [BundleState] changeset.
199    ///
200    /// ## Takes
201    /// - `bundle`: The [BundleState] changeset to apply to the trie DB.
202    ///
203    /// ## Returns
204    /// - `Ok(())` if the accounts were successfully updated.
205    /// - `Err(_)` if the accounts could not be updated.
206    fn update_accounts(&mut self, bundle: &BundleState) -> TrieDBResult<()> {
207        // Sort the storage keys prior to applying the changeset, to ensure that the order of
208        // application is deterministic between runs.
209        let mut sorted_state =
210            bundle.state().iter().map(|(k, v)| (k, keccak256(*k), v)).collect::<Vec<_>>();
211        sorted_state.sort_by_key(|(_, hashed_addr, _)| *hashed_addr);
212
213        for (address, hashed_address, bundle_account) in sorted_state {
214            if bundle_account.status.is_not_modified() {
215                continue;
216            }
217
218            // Compute the path to the account in the trie.
219            let account_path = Nibbles::unpack(hashed_address.as_slice());
220
221            // If the account was destroyed, delete it from the trie.
222            if bundle_account.was_destroyed() {
223                self.root_node.delete(&account_path, &self.fetcher, &self.hinter)?;
224                self.storage_roots.remove(address);
225                continue;
226            }
227
228            let account_info =
229                bundle_account.account_info().ok_or(TrieDBError::MissingAccountInfo)?;
230            let mut trie_account = TrieAccount {
231                balance: account_info.balance,
232                nonce: account_info.nonce,
233                code_hash: account_info.code_hash,
234                ..Default::default()
235            };
236
237            // Update the account's storage root
238            let acc_storage_root = self
239                .storage_roots
240                .entry(*address)
241                .or_insert_with(|| TrieNode::new_blinded(EMPTY_ROOT_HASH));
242
243            // Sort the hashed storage keys prior to applying the changeset, to ensure that the
244            // order of application is deterministic between runs.
245            let mut sorted_storage = bundle_account
246                .storage
247                .iter()
248                .map(|(k, v)| (keccak256(k.to_be_bytes::<32>()), v))
249                .collect::<Vec<_>>();
250            sorted_storage.sort_by_key(|(slot, _)| *slot);
251
252            sorted_storage.into_iter().try_for_each(|(hashed_key, value)| {
253                Self::change_storage(
254                    acc_storage_root,
255                    hashed_key,
256                    value,
257                    &self.fetcher,
258                    &self.hinter,
259                )
260            })?;
261
262            // Recompute the account storage root.
263            let root = acc_storage_root.blind();
264            trie_account.storage_root = root;
265
266            // RLP encode the trie account for insertion.
267            let mut account_buf = Vec::with_capacity(trie_account.length());
268            trie_account.encode(&mut account_buf);
269
270            // Insert or update the account in the trie.
271            self.root_node.insert(&account_path, account_buf.into(), &self.fetcher)?;
272        }
273
274        Ok(())
275    }
276
277    /// Modifies a storage slot of an account in the Merkle Patricia Trie.
278    ///
279    /// ## Takes
280    /// - `storage_root`: The storage root of the account.
281    /// - `hashed_key`: The hashed storage slot key.
282    /// - `value`: The new value of the storage slot.
283    /// - `fetcher`: The trie node fetcher.
284    /// - `hinter`: The trie hinter.
285    ///
286    /// ## Returns
287    /// - `Ok(())` if the storage slot was successfully modified.
288    /// - `Err(_)` if the storage slot could not be modified.
289    fn change_storage(
290        storage_root: &mut TrieNode,
291        hashed_key: B256,
292        value: &StorageSlot,
293        fetcher: &F,
294        hinter: &H,
295    ) -> TrieDBResult<()> {
296        if !value.is_changed() {
297            return Ok(());
298        }
299
300        // RLP encode the storage slot value.
301        let mut rlp_buf = Vec::with_capacity(value.present_value.length());
302        value.present_value.encode(&mut rlp_buf);
303
304        // Insert or update the storage slot in the trie.
305        let hashed_slot_key = Nibbles::unpack(hashed_key.as_slice());
306        if value.present_value.is_zero() {
307            // If the storage slot is being set to zero, prune it from the trie.
308            storage_root.delete(&hashed_slot_key, fetcher, hinter)?;
309        } else {
310            // Otherwise, update the storage slot.
311            storage_root.insert(&hashed_slot_key, rlp_buf.into(), fetcher)?;
312        }
313
314        Ok(())
315    }
316}
317
318impl<F, H> Database for TrieDB<F, H>
319where
320    F: TrieDBProvider,
321    H: TrieHinter,
322{
323    type Error = TrieDBError;
324
325    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
326        // Fetch the account from the trie.
327        let Some(trie_account) =
328            self.get_trie_account(&address, self.parent_block_header.number)?
329        else {
330            // If the account does not exist in the trie, return `Ok(None)`.
331            return Ok(None);
332        };
333
334        // Insert the account's storage root into the cache.
335        self.storage_roots.insert(address, TrieNode::new_blinded(trie_account.storage_root));
336
337        // Return a partial DB account. The storage and code are not loaded out-right, and are
338        // loaded optimistically in the `Database` + `DatabaseRef` trait implementations.
339        Ok(Some(AccountInfo {
340            balance: trie_account.balance,
341            nonce: trie_account.nonce,
342            code_hash: trie_account.code_hash,
343            code: None,
344        }))
345    }
346
347    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
348        self.fetcher
349            .bytecode_by_hash(code_hash)
350            .map(Bytecode::new_raw)
351            .map_err(|e| TrieDBError::Provider(e.to_string()))
352    }
353
354    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
355        // Send a hint to the host to fetch the storage proof.
356        self.hinter
357            .hint_storage_proof(address, index, self.parent_block_header.number)
358            .map_err(|e| TrieDBError::Provider(e.to_string()))?;
359
360        // Fetch the account's storage root from the cache. If storage is being accessed, the
361        // account should have been loaded into the cache by the `basic` method. If the account was
362        // non-existing, the storage root will not be present.
363        match self.storage_roots.get_mut(&address) {
364            None => {
365                // If the storage root for the account does not exist, return zero.
366                Ok(U256::ZERO)
367            }
368            Some(storage_root) => {
369                // Fetch the storage slot from the trie.
370                let hashed_slot_key = keccak256(index.to_be_bytes::<32>().as_slice());
371                match storage_root.open(&Nibbles::unpack(hashed_slot_key), &self.fetcher)? {
372                    Some(slot_value) => {
373                        // Decode the storage slot value.
374                        let int_slot = U256::decode(&mut slot_value.as_ref())
375                            .map_err(TrieNodeError::RLPError)?;
376                        Ok(int_slot)
377                    }
378                    None => {
379                        // If the storage slot does not exist, return zero.
380                        Ok(U256::ZERO)
381                    }
382                }
383            }
384        }
385    }
386
387    fn block_hash(&mut self, block_number: u64) -> Result<B256, Self::Error> {
388        // Copy the current header
389        let mut header = self.parent_block_header.inner().clone();
390
391        // Check if the block number is in range. If not, we can fail early.
392        if block_number > header.number ||
393            header.number.saturating_sub(block_number) > BLOCK_HASH_HISTORY
394        {
395            return Ok(B256::default());
396        }
397
398        // Walk back the block headers to the desired block number.
399        while header.number > block_number {
400            header = self
401                .fetcher
402                .header_by_hash(header.parent_hash)
403                .map_err(|e| TrieDBError::Provider(e.to_string()))?;
404        }
405
406        Ok(header.hash_slow())
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413    use alloy_consensus::Sealable;
414    use alloy_primitives::b256;
415    use kona_mpt::NoopTrieHinter;
416
417    fn new_test_db() -> TrieDB<NoopTrieDBProvider, NoopTrieHinter> {
418        TrieDB::new(
419            B256::default(),
420            Header::default().seal_slow(),
421            NoopTrieDBProvider,
422            NoopTrieHinter,
423        )
424    }
425
426    #[test]
427    fn test_trie_db_take_root_node() {
428        let db = new_test_db();
429        let root_node = db.take_root_node();
430        assert_eq!(root_node.blind(), B256::default());
431    }
432
433    #[test]
434    fn test_trie_db_root_node_ref() {
435        let db = new_test_db();
436        let root_node = db.root();
437        assert_eq!(root_node.blind(), B256::default());
438    }
439
440    #[test]
441    fn test_trie_db_storage_roots() {
442        let db = new_test_db();
443        let storage_roots = db.storage_roots();
444        assert!(storage_roots.is_empty());
445    }
446
447    #[test]
448    fn test_block_hash_above_range() {
449        let mut db = new_test_db();
450        db.parent_block_header = Header { number: 10, ..Default::default() }.seal_slow();
451        let block_number = 11;
452        let block_hash = db.block_hash(block_number).unwrap();
453        assert_eq!(block_hash, B256::default());
454    }
455
456    #[test]
457    fn test_block_hash_below_range() {
458        let mut db = new_test_db();
459        db.parent_block_header =
460            Header { number: BLOCK_HASH_HISTORY + 10, ..Default::default() }.seal_slow();
461        let block_number = 0;
462        let block_hash = db.block_hash(block_number).unwrap();
463        assert_eq!(block_hash, B256::default());
464    }
465
466    #[test]
467    fn test_block_hash_provider_missing_hash() {
468        let mut db = new_test_db();
469        db.parent_block_header = Header { number: 10, ..Default::default() }.seal_slow();
470        let block_number = 5;
471        let block_hash = db.block_hash(block_number).unwrap();
472        assert_eq!(
473            block_hash,
474            b256!("78dec18c6d7da925bbe773c315653cdc70f6444ed6c1de9ac30bdb36cff74c3b")
475        );
476    }
477}