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