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