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)]
24pub struct Command {
26 table: Tables,
28
29 #[arg(long, value_parser = maybe_json_value_parser)]
31 start_key: Option<String>,
32
33 #[arg(long, value_parser = maybe_json_value_parser)]
35 end_key: Option<String>,
36
37 #[arg(long)]
40 limit: Option<usize>,
41}
42
43impl Command {
44 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}