Skip to main content

reth_trie_db/
changesets.rs

1//! Database-backed trie changeset computation utilities.
2//!
3//! This module reconstructs trie changesets from database state. The resulting changesets contain
4//! the old trie node values needed to revert a block or contiguous range of blocks.
5
6use crate::DatabaseHashedPostState;
7use alloy_primitives::BlockNumber;
8use reth_storage_api::{BlockNumReader, ChangeSetReader, StorageChangeSetReader};
9use reth_storage_errors::provider::ProviderError;
10use reth_trie::{
11    hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory},
12    trie_cursor::{InMemoryTrieCursorFactory, TrieCursorFactory},
13    HashedPostStateSorted, StateRoot,
14};
15use reth_trie_common::{updates::TrieUpdatesSorted, TrieInputSorted};
16use std::{iter::once, ops::RangeInclusive, sync::Arc};
17use tracing::debug;
18
19/// Computes trie changesets for a block.
20///
21/// For block `N`, this reconstructs the trie as it existed after `N`, then calculates the trie
22/// updates needed to restore the state before `N`.
23///
24/// # Errors
25///
26/// Returns an error if the block exceeds the database tip, database access fails, or state root
27/// computation fails.
28pub fn compute_block_trie_changesets<Provider, StateTrieProvider>(
29    provider: &Provider,
30    state_trie_provider: &StateTrieProvider,
31    block_number: BlockNumber,
32) -> Result<TrieUpdatesSorted, ProviderError>
33where
34    Provider: ChangeSetReader + StorageChangeSetReader + BlockNumReader,
35    StateTrieProvider: TrieCursorFactory + HashedCursorFactory,
36{
37    let db_tip_block = provider.best_block_number()?;
38    compute_range_trie_changesets(
39        provider,
40        state_trie_provider,
41        &[],
42        block_number..=block_number,
43        db_tip_block,
44    )
45}
46
47/// Computes aggregate trie changesets for an inclusive block range.
48///
49/// Returns the value before `range.start()` of every trie node touched by a block in `range`,
50/// including nodes created and removed within it.
51///
52/// `state_trie_provider` must expose the complete trie and hashed state at `db_tip_block`.
53///
54/// `forward_updates` contains original executed-block trie updates on that same chain, sorted by
55/// strictly increasing block number. Consecutive available blocks are reverted together, preserving
56/// forward paths omitted by the aggregate calculation. Missing blocks are reverted individually so
57/// transient nodes are retained.
58///
59/// # Errors
60///
61/// Returns an error if the range exceeds `db_tip_block`, database access fails, or state root
62/// computation fails.
63pub fn compute_range_trie_changesets<Provider, StateTrieProvider>(
64    provider: &Provider,
65    state_trie_provider: &StateTrieProvider,
66    forward_updates: &[(BlockNumber, Arc<TrieUpdatesSorted>)],
67    range: RangeInclusive<BlockNumber>,
68    db_tip_block: BlockNumber,
69) -> Result<TrieUpdatesSorted, ProviderError>
70where
71    Provider: ChangeSetReader + StorageChangeSetReader + BlockNumReader,
72    StateTrieProvider: TrieCursorFactory + HashedCursorFactory,
73{
74    debug_assert!(
75        forward_updates.is_sorted_by(|(a, _), (b, _)| a < b),
76        "forward updates must have strictly increasing block numbers"
77    );
78
79    if range.is_empty() {
80        return Ok(TrieUpdatesSorted::default())
81    }
82
83    let start_block = *range.start();
84    let end_block = *range.end();
85
86    if end_block > db_tip_block {
87        return Err(ProviderError::InsufficientChangesets {
88            requested: end_block,
89            available: 0..=db_tip_block,
90        })
91    }
92
93    debug!(
94        target: "trie::changesets",
95        start_block,
96        end_block,
97        db_tip_block,
98        "Computing range trie changesets from database state"
99    );
100
101    let mut overlay = TrieInputSorted::default();
102    // Rewind later blocks to reconstruct the trie at the end of the requested range.
103    if end_block < db_tip_block {
104        rewind_trie_range(
105            provider,
106            state_trie_provider,
107            forward_updates,
108            (end_block + 1)..=db_tip_block,
109            &mut overlay,
110            &mut None,
111        )?;
112    }
113
114    let mut reverts = Some(TrieUpdatesSorted::default());
115    rewind_trie_range(
116        provider,
117        state_trie_provider,
118        forward_updates,
119        range,
120        &mut overlay,
121        &mut reverts,
122    )?;
123    let reverts = reverts.expect("revert collection is enabled");
124
125    debug!(
126        target: "trie::changesets",
127        start_block,
128        end_block,
129        num_account_nodes = reverts.account_nodes_ref().len(),
130        num_storage_tries = reverts.storage_tries_ref().len(),
131        "Computed range trie changesets successfully"
132    );
133
134    Ok(reverts)
135}
136
137/// Rewinds the current state and trie views, accumulating changesets when `reverts` is `Some`.
138fn rewind_trie_range<Provider, StateTrieProvider>(
139    provider: &Provider,
140    state_trie_provider: &StateTrieProvider,
141    forward_updates: &[(BlockNumber, Arc<TrieUpdatesSorted>)],
142    blocks: RangeInclusive<BlockNumber>,
143    overlay: &mut TrieInputSorted,
144    reverts: &mut Option<TrieUpdatesSorted>,
145) -> Result<(), ProviderError>
146where
147    Provider: ChangeSetReader + StorageChangeSetReader + BlockNumReader,
148    StateTrieProvider: TrieCursorFactory + HashedCursorFactory,
149{
150    let mut end = *blocks.end();
151    while end >= *blocks.start() {
152        let mut start = end;
153        let mut segment_range = 0..0;
154
155        // Select updates of consecutive blocks iterating backwards from `end`.
156        // Without the original updates, reverting one block at a time preserves nodes that
157        // appear and disappear inside the range. An endpoint-only calculation would omit them.
158        if let Ok(index) = forward_updates.binary_search_by_key(&end, |(block, _)| *block) {
159            segment_range = index..index + 1;
160            while segment_range.start > 0 && start > *blocks.start() {
161                if forward_updates[segment_range.start - 1].0 != start - 1 {
162                    break
163                }
164                segment_range.start -= 1;
165                start -= 1;
166            }
167        }
168
169        // Collect reverts for the segment and use them to generate the trie reverts for the
170        // segment.
171        let segment_state_reverts = HashedPostStateSorted::from_reverts(provider, start..=end)?;
172        let prefixes = segment_state_reverts.construct_prefix_sets().freeze();
173        Arc::make_mut(&mut overlay.state).extend_ref_and_sort(&segment_state_reverts);
174
175        let segment_trie_reverts = StateRoot::new(
176            InMemoryTrieCursorFactory::new(state_trie_provider, overlay.nodes.as_ref()),
177            HashedPostStateCursorFactory::new(state_trie_provider, overlay.state.as_ref()),
178        )
179        .with_prefix_sets(prefixes)
180        .root_with_updates()
181        .map_err(ProviderError::other)?
182        .1
183        .into_sorted();
184
185        // A path missing from the calculated reverts has the same value at both segment ends, so
186        // its newest forward update is also its value before the segment. Merging the forward
187        // updates keeps nodes that appear and disappear inside the segment, e.g. a node created
188        // in block 6 and deleted in block 8 when reverting 6..=8.
189        let segment_trie_forward =
190            forward_updates[segment_range].iter().rev().map(|(_, updates)| updates.as_ref());
191        let segment_trie_reverts =
192            TrieUpdatesSorted::merge_iter(once(&segment_trie_reverts).chain(segment_trie_forward));
193
194        Arc::make_mut(&mut overlay.nodes).extend_ref_and_sort(&segment_trie_reverts);
195        if let Some(reverts) = reverts {
196            reverts.extend_ref_and_sort(&segment_trie_reverts);
197        }
198
199        if start == *blocks.start() {
200            break
201        }
202        end = start - 1;
203    }
204
205    Ok(())
206}