use crate::db::get::{maybe_json_value_parser, table_key}; use ahash::RandomState; use clap::Parser; use reth_db::{DatabaseEnv, RawKey, RawTable, RawValue, TableViewer, Tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, table::Table, transaction::DbTx}; use reth_db_common::DbTool; use std::{ hash::{BuildHasher, Hasher}, sync::Arc, time::{Duration, Instant}, }; use tracing::{info, warn}; #[derive(Parser, Debug)] /// The arguments for the `reth db checksum` command pub struct Command { /// The table name table: Tables, /// The start of the range to checksum. #[arg(long, value_parser = maybe_json_value_parser)] start_key: Option, /// The end of the range to checksum. #[arg(long, value_parser = maybe_json_value_parser)] end_key: Option, /// The maximum number of records that are queried and used to compute the /// checksum. #[arg(long)] limit: Option, } impl Command { /// Execute `db checksum` command pub fn execute(self, tool: &DbTool>) -> eyre::Result<()> { warn!("This command should be run without the node running!"); self.table.view(&ChecksumViewer { tool, start_key: self.start_key, end_key: self.end_key, limit: self.limit, })?; Ok(()) } } pub(crate) struct ChecksumViewer<'a, DB: Database> { tool: &'a DbTool, start_key: Option, end_key: Option, limit: Option, } impl ChecksumViewer<'_, DB> { pub(crate) const fn new(tool: &'_ DbTool) -> ChecksumViewer<'_, DB> { ChecksumViewer { tool, start_key: None, end_key: None, limit: None } } } impl TableViewer<(u64, Duration)> for ChecksumViewer<'_, DB> { type Error = eyre::Report; fn view(&self) -> Result<(u64, Duration), Self::Error> { let provider = self.tool.provider_factory.provider()?.disable_long_read_transaction_safety(); let tx = provider.tx_ref(); info!( "Start computing checksum, start={:?}, end={:?}, limit={:?}", self.start_key, self.end_key, self.limit ); let mut cursor = tx.cursor_read::>()?; let walker = match (self.start_key.as_deref(), self.end_key.as_deref()) { (Some(start), Some(end)) => { let start_key = table_key::(start).map(RawKey::::new)?; let end_key = table_key::(end).map(RawKey::::new)?; cursor.walk_range(start_key..=end_key)? } (None, Some(end)) => { let end_key = table_key::(end).map(RawKey::::new)?; cursor.walk_range(..=end_key)? } (Some(start), None) => { let start_key = table_key::(start).map(RawKey::::new)?; cursor.walk_range(start_key..)? } (None, None) => cursor.walk_range(..)?, }; let start_time = Instant::now(); let mut hasher = RandomState::with_seeds(1, 2, 3, 4).build_hasher(); let mut total = 0; let limit = self.limit.unwrap_or(usize::MAX); let mut enumerate_start_key = None; let mut enumerate_end_key = None; for (index, entry) in walker.enumerate() { let (k, v): (RawKey, RawValue) = entry?; if index % 100_000 == 0 { info!("Hashed {index} entries."); } hasher.write(k.raw_key()); hasher.write(v.raw_value()); if enumerate_start_key.is_none() { enumerate_start_key = Some(k.clone()); } enumerate_end_key = Some(k); total = index + 1; if total >= limit { break } } info!("Hashed {total} entries."); if let (Some(s), Some(e)) = (enumerate_start_key, enumerate_end_key) { info!("start-key: {}", serde_json::to_string(&s.key()?).unwrap_or_default()); info!("end-key: {}", serde_json::to_string(&e.key()?).unwrap_or_default()); } let checksum = hasher.finish(); let elapsed = start_time.elapsed(); info!("Checksum for table `{}`: {:#x} (elapsed: {:?})", T::NAME, checksum, elapsed); Ok((checksum, elapsed)) } }