reth_cli_commands/db/
checksum.rs1use 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)]
21pub struct Command {
23 table: Tables,
25
26 #[arg(long, value_parser = maybe_json_value_parser)]
28 start_key: Option<String>,
29
30 #[arg(long, value_parser = maybe_json_value_parser)]
32 end_key: Option<String>,
33
34 #[arg(long)]
37 limit: Option<usize>,
38}
39
40impl Command {
41 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}