Skip to main content

reth_trie/hashed_cursor/
mod.rs

1use alloy_primitives::{keccak256, Address, B256, U256};
2use reth_primitives_traits::Account;
3use reth_storage_errors::db::DatabaseError;
4use reth_trie_common::HashedPostState;
5use revm::database::BundleAccount;
6
7/// Implementation of hashed state cursor traits for the post state.
8mod post_state;
9pub use post_state::*;
10
11/// Implementation of noop hashed state cursor.
12pub mod noop;
13
14/// Mock trie cursor implementations.
15#[cfg(any(test, feature = "test-utils"))]
16pub mod mock;
17
18/// Metrics tracking hashed cursor implementations.
19#[cfg(feature = "metrics")]
20pub mod metrics;
21#[cfg(feature = "metrics")]
22pub use metrics::{HashedCursorMetrics, InstrumentedHashedCursor, InstrumentedHashedCursorFactory};
23
24/// Uses the underlying factory directly when cursor metrics are disabled.
25#[cfg(not(feature = "metrics"))]
26pub type InstrumentedHashedCursorFactory<F> = F;
27
28/// The factory trait for creating cursors over the hashed state.
29#[auto_impl::auto_impl(&)]
30pub trait HashedCursorFactory {
31    /// The hashed account cursor type.
32    type AccountCursor<'a>: HashedCursor<Value = Account>
33    where
34        Self: 'a;
35    /// The hashed storage cursor type.
36    type StorageCursor<'a>: HashedStorageCursor<Value = U256>
37    where
38        Self: 'a;
39
40    /// Returns a cursor for iterating over all hashed accounts in the state.
41    fn hashed_account_cursor(&self) -> Result<Self::AccountCursor<'_>, DatabaseError>;
42
43    /// Returns a cursor for iterating over all hashed storage entries in the state.
44    fn hashed_storage_cursor(
45        &self,
46        hashed_address: B256,
47    ) -> Result<Self::StorageCursor<'_>, DatabaseError>;
48}
49
50/// The cursor for iterating over hashed entries.
51#[auto_impl::auto_impl(&mut)]
52pub trait HashedCursor {
53    /// Value returned by the cursor.
54    type Value: std::fmt::Debug;
55
56    /// Seek an entry greater than or equal to the given key and position the cursor there.
57    /// Returns the first entry with the key greater than or equal to the sought key.
58    fn seek(&mut self, key: B256) -> Result<Option<(B256, Self::Value)>, DatabaseError>;
59
60    /// Move the cursor to the next entry and return it.
61    fn next(&mut self) -> Result<Option<(B256, Self::Value)>, DatabaseError>;
62
63    /// Reset the cursor to its initial state.
64    ///
65    /// # Important
66    ///
67    /// After calling this method, the subsequent operation MUST be a [`HashedCursor::seek`] call.
68    fn reset(&mut self);
69}
70
71/// The cursor for iterating over hashed storage entries.
72#[auto_impl::auto_impl(&mut)]
73pub trait HashedStorageCursor: HashedCursor {
74    /// Returns `true` if the current hashed address has no storage entries.
75    ///
76    /// May be called immediately after [`Self::set_hashed_address`], without first seeking.
77    /// This may change the cursor position; seek before iterating entries afterward.
78    fn is_storage_empty(&mut self) -> Result<bool, DatabaseError>;
79
80    /// Set the hashed address for the storage cursor.
81    ///
82    /// Call [`HashedCursor::seek`] before iterating entries with [`HashedCursor::next`].
83    /// [`Self::is_storage_empty`] may be called before seeking.
84    fn set_hashed_address(&mut self, hashed_address: B256);
85}
86
87/// Materializes storage deletions for destroyed accounts as explicit zero-valued slot updates.
88///
89/// Accounts absent from the bundle pre-state are skipped because they cannot have parent storage.
90/// Final bundle values take precedence so that destroy-then-recreate transitions retain storage
91/// written by the recreated account.
92pub fn zero_destroyed_account_storage<'a>(
93    cursor_factory: &impl HashedCursorFactory,
94    accounts: impl IntoIterator<Item = (&'a Address, &'a BundleAccount)>,
95    hashed_state: &mut HashedPostState,
96) -> Result<(), DatabaseError> {
97    let mut destroyed_accounts = accounts
98        .into_iter()
99        .filter(|(_, account)| account.was_destroyed() && account.original_info.is_some())
100        .map(|(address, _)| keccak256(address));
101    let Some(mut hashed_address) = destroyed_accounts.next() else { return Ok(()) };
102    let mut cursor = cursor_factory.hashed_storage_cursor(hashed_address)?;
103
104    loop {
105        if let Some((hashed_slot, _)) = cursor.seek(B256::ZERO)? {
106            let storage = &mut hashed_state.storages.entry(hashed_address).or_default().storage;
107            storage.entry(hashed_slot).or_insert(U256::ZERO);
108            while let Some((hashed_slot, _)) = cursor.next()? {
109                storage.entry(hashed_slot).or_insert(U256::ZERO);
110            }
111        }
112
113        let Some(next_hashed_address) = destroyed_accounts.next() else { break };
114        hashed_address = next_hashed_address;
115        cursor.set_hashed_address(hashed_address);
116    }
117
118    Ok(())
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use revm::database::AccountStatus;
125
126    #[test]
127    fn zero_destroyed_storage_skips_new_accounts() {
128        let address = Address::with_last_byte(1);
129        let account = BundleAccount::new(None, None, Default::default(), AccountStatus::Destroyed);
130        let mut hashed_state = HashedPostState::default();
131
132        zero_destroyed_account_storage(
133            &mock::MockHashedCursorFactory::default(),
134            [(&address, &account)],
135            &mut hashed_state,
136        )
137        .unwrap();
138
139        assert!(hashed_state.storages.is_empty());
140    }
141}