diff --git a/bin/reth/src/commands/db/checksum.rs b/bin/reth/src/commands/db/checksum.rs index 7079f4e46..689b6ca5a 100644 --- a/bin/reth/src/commands/db/checksum.rs +++ b/bin/reth/src/commands/db/checksum.rs @@ -5,7 +5,10 @@ use reth_db::{ cursor::DbCursorRO, database::Database, table::Table, transaction::DbTx, DatabaseEnv, RawKey, RawTable, RawValue, TableViewer, Tables, }; -use std::{hash::Hasher, time::Instant}; +use std::{ + hash::Hasher, + time::{Duration, Instant}, +}; use tracing::{info, warn}; #[derive(Parser, Debug)] @@ -18,20 +21,21 @@ pub struct Command { 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 }) } } -struct ChecksumViewer<'a, DB: Database> { +pub(crate) struct ChecksumViewer<'a, DB: Database> { tool: &'a DbTool, } -impl TableViewer<()> for ChecksumViewer<'_, DB> { - type Error = eyre::Report; - - fn view(&self) -> Result<(), Self::Error> { - warn!("This command should be run without the node running!"); +impl ChecksumViewer<'_, DB> { + pub(crate) fn new(tool: &'_ DbTool) -> ChecksumViewer<'_, DB> { + ChecksumViewer { tool } + } + pub(crate) fn get_checksum(&self) -> Result<(u64, Duration), eyre::Report> { let provider = self.tool.provider_factory.provider()?.disable_long_read_transaction_safety(); let tx = provider.tx_ref(); @@ -52,8 +56,19 @@ impl TableViewer<()> for ChecksumViewer<'_, DB> { hasher.write(v.raw_value()); } + let checksum = hasher.finish(); let elapsed = start_time.elapsed(); - info!("{} checksum: {:x}, took {:?}", T::NAME, hasher.finish(), elapsed); + + Ok((checksum, elapsed)) + } +} + +impl TableViewer<()> for ChecksumViewer<'_, DB> { + type Error = eyre::Report; + + fn view(&self) -> Result<(), Self::Error> { + let (checksum, elapsed) = self.get_checksum::()?; + info!("Checksum for table `{}`: {:#x} (elapsed: {:?})", T::NAME, checksum, elapsed); Ok(()) } diff --git a/bin/reth/src/commands/db/stats.rs b/bin/reth/src/commands/db/stats.rs index 6e31e31e1..5ffc136dd 100644 --- a/bin/reth/src/commands/db/stats.rs +++ b/bin/reth/src/commands/db/stats.rs @@ -1,13 +1,24 @@ -use crate::utils::DbTool; +use std::time::Duration; + +use crate::{commands::db::checksum::ChecksumViewer, utils::DbTool}; use clap::Parser; use comfy_table::{Cell, Row, Table as ComfyTable}; use eyre::WrapErr; use human_bytes::human_bytes; use itertools::Itertools; -use reth_db::{database::Database, mdbx, static_file::iter_static_files, DatabaseEnv, Tables}; +use reth_db::{ + database::Database, mdbx, static_file::iter_static_files, AccountChangeSets, AccountsHistory, + AccountsTrie, BlockBodyIndices, BlockOmmers, BlockWithdrawals, Bytecodes, CanonicalHeaders, + DatabaseEnv, HashedAccounts, HashedStorages, HeaderNumbers, HeaderTerminalDifficulties, + Headers, PlainAccountState, PlainStorageState, PruneCheckpoints, Receipts, + StageCheckpointProgresses, StageCheckpoints, StorageChangeSets, StoragesHistory, StoragesTrie, + Tables, TransactionBlocks, TransactionHashNumbers, TransactionSenders, Transactions, + VersionHistory, +}; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_primitives::static_file::{find_fixed_range, SegmentRangeInclusive}; use reth_provider::providers::StaticFileProvider; +use tracing::info; #[derive(Parser, Debug)] /// The arguments for the `reth db stats` command @@ -15,9 +26,19 @@ pub struct Command { /// Show only the total size for static files. #[arg(long, default_value_t = false)] only_total_size: bool, + /// Show only the summary per static file segment. #[arg(long, default_value_t = false)] summary: bool, + + /// Show a checksum of each table in the database. + /// + /// WARNING: this option will take a long time to run, as it needs to traverse and hash the + /// entire database. + /// + /// For individual table checksums, use the `reth db checksum` command. + #[arg(long, default_value_t = false)] + checksum: bool, } impl Command { @@ -27,6 +48,12 @@ impl Command { data_dir: ChainPath, tool: &DbTool, ) -> eyre::Result<()> { + if self.checksum { + let checksum_report = self.checksum_report(tool)?; + println!("{checksum_report}"); + println!("\n"); + } + let static_files_stats_table = self.static_files_stats_table(data_dir)?; println!("{static_files_stats_table}"); @@ -285,4 +312,81 @@ impl Command { Ok(table) } + + fn checksum_report(&self, tool: &DbTool) -> eyre::Result { + let mut table = ComfyTable::new(); + table.load_preset(comfy_table::presets::ASCII_MARKDOWN); + table.set_header(vec![Cell::new("Table"), Cell::new("Checksum"), Cell::new("Elapsed")]); + + let db_tables = Tables::ALL; + let mut total_elapsed = Duration::default(); + + for db_table in db_tables { + info!("Calculating checksum for table: {}", db_table); + + let viewer = ChecksumViewer::new(tool); + let (checksum, elapsed) = match db_table { + Tables::AccountsHistory => viewer.get_checksum::().unwrap(), + Tables::AccountChangeSets => viewer.get_checksum::().unwrap(), + Tables::AccountsTrie => viewer.get_checksum::().unwrap(), + Tables::BlockBodyIndices => viewer.get_checksum::().unwrap(), + Tables::BlockOmmers => viewer.get_checksum::().unwrap(), + Tables::BlockWithdrawals => viewer.get_checksum::().unwrap(), + Tables::Bytecodes => viewer.get_checksum::().unwrap(), + Tables::CanonicalHeaders => viewer.get_checksum::().unwrap(), + Tables::HashedAccounts => viewer.get_checksum::().unwrap(), + Tables::HashedStorages => viewer.get_checksum::().unwrap(), + Tables::HeaderNumbers => viewer.get_checksum::().unwrap(), + Tables::HeaderTerminalDifficulties => { + viewer.get_checksum::().unwrap() + } + Tables::Headers => viewer.get_checksum::().unwrap(), + Tables::PlainAccountState => viewer.get_checksum::().unwrap(), + Tables::PlainStorageState => viewer.get_checksum::().unwrap(), + Tables::PruneCheckpoints => viewer.get_checksum::().unwrap(), + Tables::Receipts => viewer.get_checksum::().unwrap(), + Tables::StageCheckpointProgresses => { + viewer.get_checksum::().unwrap() + } + Tables::StageCheckpoints => viewer.get_checksum::().unwrap(), + Tables::StorageChangeSets => viewer.get_checksum::().unwrap(), + Tables::StoragesHistory => viewer.get_checksum::().unwrap(), + Tables::StoragesTrie => viewer.get_checksum::().unwrap(), + Tables::TransactionBlocks => viewer.get_checksum::().unwrap(), + Tables::TransactionHashNumbers => { + viewer.get_checksum::().unwrap() + } + Tables::TransactionSenders => viewer.get_checksum::().unwrap(), + Tables::Transactions => viewer.get_checksum::().unwrap(), + Tables::VersionHistory => viewer.get_checksum::().unwrap(), + }; + + // increment duration for final report + total_elapsed += elapsed; + + // add rows containing checksums to the table + let mut row = Row::new(); + row.add_cell(Cell::new(db_table)); + row.add_cell(Cell::new(format!("{:x}", checksum))); + row.add_cell(Cell::new(format!("{:?}", elapsed))); + table.add_row(row); + } + + // add a separator for the final report + let max_widths = table.column_max_content_widths(); + let mut separator = Row::new(); + for width in max_widths { + separator.add_cell(Cell::new(&"-".repeat(width as usize))); + } + table.add_row(separator); + + // add the final report + let mut row = Row::new(); + row.add_cell(Cell::new("Total elapsed")); + row.add_cell(Cell::new("")); + row.add_cell(Cell::new(format!("{:?}", total_elapsed))); + table.add_row(row); + + Ok(table) + } }