Skip to main content

reth_e2e_test_utils/
trie.rs

1//! Helpers for verifying the persisted state and trie representation of test nodes.
2
3use crate::TmpDB;
4use alloy_consensus::BlockHeader;
5use eyre::{ensure, eyre, Result};
6use reth_provider::{
7    BlockNumReader, DBProvider, DatabaseProviderFactory, HeaderProvider, StorageSettingsCache,
8};
9use reth_trie::{
10    prefix_set::{PrefixSetMut, TriePrefixSets},
11    verify::{default_storage_workers, Output, DEFAULT_STORAGE_LOOKAHEAD},
12    StateRoot,
13};
14use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory, DatabaseVerifier};
15
16/// Asserts that the persisted state and trie representation is internally consistent and matches
17/// the state root of the latest persisted block.
18///
19/// This performs two checks against persisted database state:
20/// - the state root recomputed from the persisted hashed state matches the state root of the latest
21///   persisted header, and
22/// - the persisted trie nodes match a recomputation from the hashed state (the library equivalent
23///   of a `reth db repair-trie --dry-run`).
24///
25/// Only on-disk data is read, so callers must ensure the blocks of interest have been persisted,
26/// e.g. via [`NodeTestContext::wait_for_persisted_block`].
27///
28/// [`NodeTestContext::wait_for_persisted_block`]: crate::node::NodeTestContext::wait_for_persisted_block
29pub fn assert_trie_consistency<F>(factory: &F) -> Result<()>
30where
31    F: DatabaseProviderFactory<
32            DB = TmpDB,
33            Provider: DBProvider + StorageSettingsCache + BlockNumReader + HeaderProvider,
34        > + AsRef<F::DB>,
35{
36    // Prevent commits while opening the independent snapshots.
37    let snapshot_guard = factory.database_provider_rw()?;
38    let provider = factory.database_provider_ro()?;
39    let tip = provider.best_block_number()?;
40    let header = provider
41        .header_by_number(tip)?
42        .ok_or_else(|| eyre!("missing persisted header for block {tip}"))?;
43    let tx = provider.tx_ref();
44
45    reth_trie_db::with_adapter!(provider, |A| {
46        let db = factory.as_ref().db().clone().with_disable_long_read_transaction_safety();
47        let verifier = DatabaseVerifier::<_, A>::new(
48            &db,
49            default_storage_workers(),
50            DEFAULT_STORAGE_LOOKAHEAD,
51        )?;
52        drop(snapshot_guard);
53
54        // Recompute all account leaves from the persisted hashed state. Storage roots are taken
55        // from the persisted storage tries, which the verifier checks against the hashed
56        // storage tables.
57        let recomputed = StateRoot::new(
58            DatabaseTrieCursorFactory::<_, A>::new(tx),
59            DatabaseHashedCursorFactory::new(tx),
60        )
61        .with_prefix_sets(TriePrefixSets {
62            account_prefix_set: PrefixSetMut::all().freeze(),
63            ..Default::default()
64        })
65        .root()?;
66        ensure!(
67            recomputed == header.state_root(),
68            "state root {recomputed} recomputed from the persisted state does not match the state root of persisted block {tip}: {}",
69            header.state_root()
70        );
71
72        let mut inconsistencies = Vec::new();
73        for output in verifier {
74            match output? {
75                Output::Progress(_) => {}
76                inconsistency => inconsistencies.push(inconsistency),
77            }
78        }
79        ensure!(
80            inconsistencies.is_empty(),
81            "persisted trie is inconsistent with the persisted hashed state: {inconsistencies:?}"
82        );
83
84        Ok(())
85    })
86}