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};
14use std::time::{Duration, Instant};
15
16/// Waits until the node has persisted at least the given block number to disk.
17///
18/// The engine keeps the most recent blocks in memory, so tests that want to inspect the
19/// database, e.g. via [`assert_trie_consistency`], must first advance the chain far enough and
20/// wait for the persistence task to catch up. Unlike `NodeTestContext::wait_block` this only
21/// needs the block number and fails after `timeout` instead of polling indefinitely.
22pub async fn wait_for_persisted_block<F>(factory: &F, number: u64, timeout: Duration) -> Result<()>
23where
24    F: DatabaseProviderFactory<Provider: BlockNumReader>,
25{
26    let start = Instant::now();
27    loop {
28        // the finish checkpoint is committed in the same transaction as the block's state
29        if factory.database_provider_ro()?.best_block_number()? >= number {
30            return Ok(())
31        }
32        ensure!(start.elapsed() < timeout, "timed out waiting for block {number} to be persisted");
33        tokio::time::sleep(Duration::from_millis(50)).await;
34    }
35}
36
37/// Asserts that the persisted state and trie representation is internally consistent and matches
38/// the state root of the latest persisted block.
39///
40/// This performs two checks against a single database snapshot:
41/// - the state root recomputed from the persisted hashed state matches the state root of the latest
42///   persisted header, and
43/// - the persisted trie nodes match a recomputation from the hashed state (the library equivalent
44///   of a `reth db repair-trie --dry-run`).
45///
46/// Only on-disk data is read, so callers must ensure the blocks of interest have been persisted,
47/// e.g. via [`wait_for_persisted_block`].
48pub fn assert_trie_consistency<F>(factory: &F) -> Result<()>
49where
50    F: DatabaseProviderFactory<
51        Provider: DBProvider + StorageSettingsCache + BlockNumReader + HeaderProvider,
52    >,
53{
54    let provider = factory.database_provider_ro()?;
55    let tip = provider.best_block_number()?;
56    let header = provider
57        .header_by_number(tip)?
58        .ok_or_else(|| eyre!("missing persisted header for block {tip}"))?;
59    let tx = provider.tx_ref();
60
61    reth_trie_db::with_adapter!(provider, |A| {
62        // Recompute all account leaves from the persisted hashed state. Storage roots are taken
63        // from the persisted storage tries, which the verifier below checks against the hashed
64        // storage tables.
65        let recomputed = StateRoot::new(
66            DatabaseTrieCursorFactory::<_, A>::new(tx),
67            DatabaseHashedCursorFactory::new(tx),
68        )
69        .with_prefix_sets(TriePrefixSets {
70            account_prefix_set: PrefixSetMut::all().freeze(),
71            ..Default::default()
72        })
73        .root()?;
74        ensure!(
75            recomputed == header.state_root(),
76            "state root {recomputed} recomputed from the persisted state does not match the state root of persisted block {tip}: {}",
77            header.state_root()
78        );
79
80        let trie_cursor_factory = DatabaseTrieCursorFactory::<_, A>::new(tx);
81        let verifier = Verifier::new(&trie_cursor_factory, DatabaseHashedCursorFactory::new(tx))?;
82        let mut inconsistencies = Vec::new();
83        for output in verifier {
84            match output? {
85                Output::Progress(_) => {}
86                inconsistency => inconsistencies.push(inconsistency),
87            }
88        }
89        ensure!(
90            inconsistencies.is_empty(),
91            "persisted trie is inconsistent with the persisted hashed state: {inconsistencies:?}"
92        );
93
94        Ok(())
95    })
96}