Skip to main content

reth_trie_db/
prefix_set.rs

1use alloy_primitives::{
2    keccak256,
3    map::{HashMap, HashSet},
4    Address, BlockNumber, B256,
5};
6use core::ops::RangeInclusive;
7use reth_db_api::{
8    cursor::DbCursorRO,
9    models::{AccountBeforeTx, BlockNumberAddress},
10    tables,
11    transaction::DbTx,
12};
13use reth_storage_api::{ChangeSetReader, DBProvider, StorageChangeSetReader};
14use reth_storage_errors::provider::ProviderError;
15use reth_trie::{
16    prefix_set::{PrefixSetMut, TriePrefixSets},
17    Nibbles,
18};
19
20/// Load prefix sets using a provider that implements [`ChangeSetReader`]. This function can read
21/// changesets from both static files and database.
22pub fn load_prefix_sets_with_provider<Provider>(
23    provider: &Provider,
24    range: RangeInclusive<BlockNumber>,
25) -> Result<TriePrefixSets, ProviderError>
26where
27    Provider: ChangeSetReader + StorageChangeSetReader + DBProvider,
28{
29    let tx = provider.tx_ref();
30
31    // Initialize prefix sets.
32    let mut account_prefix_set = PrefixSetMut::default();
33    let mut storage_prefix_sets = HashMap::<B256, PrefixSetMut>::default();
34    let mut destroyed_accounts = HashSet::default();
35
36    // Get account changesets using the provider (handles static files + database)
37    let account_changesets = provider.account_changesets_range(*range.start()..*range.end() + 1)?;
38
39    // We still need direct access to HashedAccounts table
40    let mut account_hashed_state_cursor = tx.cursor_read::<tables::HashedAccounts>()?;
41
42    for (_, AccountBeforeTx { address, .. }) in account_changesets {
43        let hashed_address = keccak256(address);
44        account_prefix_set.insert(Nibbles::unpack(hashed_address));
45
46        if account_hashed_state_cursor.seek_exact(hashed_address)?.is_none() {
47            destroyed_accounts.insert(hashed_address);
48        }
49    }
50
51    // Walk storage changesets using the provider (handles static files + database)
52    let storage_changesets = provider.storage_changesets_range(range)?;
53    // Rows are ordered by `BlockNumberAddress`, so all slots of one account arrive consecutively.
54    // Memoizing the last address avoids re-hashing it once per slot; the repeated account prefix
55    // insert can be skipped because `PrefixSetMut::freeze` deduplicates anyway.
56    let mut last_address: Option<(Address, B256)> = None;
57    for (BlockNumberAddress((_, address)), storage_entry) in storage_changesets {
58        let hashed_address = match last_address {
59            Some((last, hashed)) if last == address => hashed,
60            _ => {
61                let hashed = keccak256(address);
62                account_prefix_set.insert(Nibbles::unpack(hashed));
63                last_address = Some((address, hashed));
64                hashed
65            }
66        };
67        storage_prefix_sets
68            .entry(hashed_address)
69            .or_default()
70            .insert(Nibbles::unpack(keccak256(storage_entry.key)));
71    }
72
73    Ok(TriePrefixSets {
74        account_prefix_set: account_prefix_set.freeze(),
75        storage_prefix_sets: storage_prefix_sets
76            .into_iter()
77            .map(|(k, v)| (k, v.freeze()))
78            .collect(),
79        destroyed_accounts,
80    })
81}