reth_trie/hashed_cursor/
mod.rs1use 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
6mod post_state;
8pub use post_state::*;
9
10pub mod noop;
12
13#[cfg(any(test, feature = "test-utils"))]
15pub mod mock;
16
17#[cfg(feature = "metrics")]
19pub mod metrics;
20#[cfg(feature = "metrics")]
21pub use metrics::{HashedCursorMetrics, InstrumentedHashedCursor, InstrumentedHashedCursorFactory};
22
23#[cfg(not(feature = "metrics"))]
25pub type InstrumentedHashedCursorFactory<F> = F;
26
27#[auto_impl::auto_impl(&)]
29pub trait HashedCursorFactory {
30 type AccountCursor<'a>: HashedCursor<Value = Account>
32 where
33 Self: 'a;
34 type StorageCursor<'a>: HashedStorageCursor<Value = U256>
36 where
37 Self: 'a;
38
39 fn hashed_account_cursor(&self) -> Result<Self::AccountCursor<'_>, DatabaseError>;
41
42 fn hashed_storage_cursor(
44 &self,
45 hashed_address: B256,
46 ) -> Result<Self::StorageCursor<'_>, DatabaseError>;
47}
48
49#[auto_impl::auto_impl(&mut)]
51pub trait HashedCursor {
52 type Value: std::fmt::Debug;
54
55 fn seek(&mut self, key: B256) -> Result<Option<(B256, Self::Value)>, DatabaseError>;
58
59 fn next(&mut self) -> Result<Option<(B256, Self::Value)>, DatabaseError>;
61
62 fn reset(&mut self);
68}
69
70#[auto_impl::auto_impl(&mut)]
72pub trait HashedStorageCursor: HashedCursor {
73 fn is_storage_empty(&mut self) -> Result<bool, DatabaseError>;
78
79 fn set_hashed_address(&mut self, hashed_address: B256);
84}
85
86pub 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}