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