reth_cli_commands/db/
checksum.rs

1use crate::{
2    common::CliNodeTypes,
3    db::get::{maybe_json_value_parser, table_key},
4};
5use ahash::RandomState;
6use clap::Parser;
7use reth_chainspec::EthereumHardforks;
8use reth_db::{DatabaseEnv, RawKey, RawTable, RawValue, TableViewer, Tables};
9use reth_db_api::{cursor::DbCursorRO, table::Table, transaction::DbTx};
10use reth_db_common::DbTool;
11use reth_node_builder::{NodeTypesWithDB, NodeTypesWithDBAdapter};
12use reth_provider::{providers::ProviderNodeTypes, DBProvider};
13use std::{
14    hash::{BuildHasher, Hasher},
15    sync::Arc,
16    time::{Duration, Instant},
17};
18use tracing::{info, warn};
19
20#[derive(Parser, Debug)]
21/// The arguments for the `reth db checksum` command
22pub struct Command {
23    /// The table name
24    table: Tables,
25
26    /// The start of the range to checksum.
27    #[arg(long, value_parser = maybe_json_value_parser)]
28    start_key: Option<String>,
29
30    /// The end of the range to checksum.
31    #[arg(long, value_parser = maybe_json_value_parser)]
32    end_key: Option<String>,
33
34    /// The maximum number of records that are queried and used to compute the
35    /// checksum.
36    #[arg(long)]
37    limit: Option<usize>,
38}
39
40impl Command {
41    /// Execute `db checksum` command
42    pub fn execute<N: CliNodeTypes<ChainSpec: EthereumHardforks>>(
43        self,
44        tool: &DbTool<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
45    ) -> eyre::Result<()> {
46        warn!("This command should be run without the node running!");
47        self.table.view(&ChecksumViewer {
48            tool,
49            start_key: self.start_key,
50            end_key: self.end_key,
51            limit: self.limit,
52        })?;
53        Ok(())
54    }
55}
56
57pub(crate) struct ChecksumViewer<'a, N: NodeTypesWithDB> {
58    tool: &'a DbTool<N>,
59    start_key: Option<String>,
60    end_key: Option<String>,
61    limit: Option<usize>,
62}
63
64impl<N: NodeTypesWithDB> ChecksumViewer<'_, N> {
65    pub(crate) const fn new(tool: &'_ DbTool<N>) -> ChecksumViewer<'_, N> {
66        ChecksumViewer { tool, start_key: None, end_key: None, limit: None }
67    }
68}
69
70impl<N: ProviderNodeTypes> TableViewer<(u64, Duration)> for ChecksumViewer<'_, N> {
71    type Error = eyre::Report;
72
73    fn view<T: Table>(&self) -> Result<(u64, Duration), Self::Error> {
74        let provider =
75            self.tool.provider_factory.provider()?.disable_long_read_transaction_safety();
76        let tx = provider.tx_ref();
77        info!(
78            "Start computing checksum, start={:?}, end={:?}, limit={:?}",
79            self.start_key, self.end_key, self.limit
80        );
81
82        let mut cursor = tx.cursor_read::<RawTable<T>>()?;
83        let walker = match (self.start_key.as_deref(), self.end_key.as_deref()) {
84            (Some(start), Some(end)) => {
85                let start_key = table_key::<T>(start).map(RawKey::new)?;
86                let end_key = table_key::<T>(end).map(RawKey::new)?;
87                cursor.walk_range(start_key..=end_key)?
88            }
89            (None, Some(end)) => {
90                let end_key = table_key::<T>(end).map(RawKey::new)?;
91
92                cursor.walk_range(..=end_key)?
93            }
94            (Some(start), None) => {
95                let start_key = table_key::<T>(start).map(RawKey::new)?;
96                cursor.walk_range(start_key..)?
97            }
98            (None, None) => cursor.walk_range(..)?,
99        };
100
101        let start_time = Instant::now();
102        let mut hasher = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
103        let mut total = 0;
104
105        let limit = self.limit.unwrap_or(usize::MAX);
106        let mut enumerate_start_key = None;
107        let mut enumerate_end_key = None;
108        for (index, entry) in walker.enumerate() {
109            let (k, v): (RawKey<T::Key>, RawValue<T::Value>) = entry?;
110
111            if index % 100_000 == 0 {
112                info!("Hashed {index} entries.");
113            }
114
115            hasher.write(k.raw_key());
116            hasher.write(v.raw_value());
117
118            if enumerate_start_key.is_none() {
119                enumerate_start_key = Some(k.clone());
120            }
121            enumerate_end_key = Some(k);
122
123            total = index + 1;
124            if total >= limit {
125                break
126            }
127        }
128
129        info!("Hashed {total} entries.");
130        if let (Some(s), Some(e)) = (enumerate_start_key, enumerate_end_key) {
131            info!("start-key: {}", serde_json::to_string(&s.key()?).unwrap_or_default());
132            info!("end-key: {}", serde_json::to_string(&e.key()?).unwrap_or_default());
133        }
134
135        let checksum = hasher.finish();
136        let elapsed = start_time.elapsed();
137
138        info!("Checksum for table `{}`: {:#x} (elapsed: {:?})", T::NAME, checksum, elapsed);
139
140        Ok((checksum, elapsed))
141    }
142}