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