1use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
2use clap::{Parser, Subcommand};
3use reth_chainspec::{EthChainSpec, EthereumHardforks};
4use reth_cli::chainspec::ChainSpecParser;
5use reth_db::version::{get_db_version, DatabaseVersionError, DB_VERSION};
6use reth_db_common::DbTool;
7use std::{
8 io::{self, Write},
9 sync::Arc,
10};
11mod account_storage;
12mod checksum;
13mod clear;
14mod diff;
15mod get;
16mod list;
17mod repair_trie;
18mod settings;
19mod static_file_header;
20mod stats;
21mod tui;
23
24#[derive(Debug, Parser)]
26pub struct Command<C: ChainSpecParser> {
27 #[command(flatten)]
28 env: EnvironmentArgs<C>,
29
30 #[command(subcommand)]
31 command: Subcommands,
32}
33
34#[derive(Subcommand, Debug)]
35pub enum Subcommands {
37 Stats(stats::Command),
39 List(list::Command),
41 Checksum(checksum::Command),
43 Diff(diff::Command),
45 Get(get::Command),
47 Drop {
49 #[arg(short, long)]
51 force: bool,
52 },
53 Clear(clear::Command),
55 RepairTrie(repair_trie::Command),
57 StaticFileHeader(static_file_header::Command),
59 Version,
61 Path,
63 Settings(settings::Command),
65 AccountStorage(account_storage::Command),
67}
68
69macro_rules! db_exec {
72 ($env:expr, $tool:ident, $N:ident, $access_rights:expr, $command:block) => {
73 let Environment { provider_factory, .. } = $env.init::<$N>($access_rights)?;
74
75 let $tool = DbTool::new(provider_factory)?;
76 $command;
77 };
78}
79
80impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
81 pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
83 let data_dir = self.env.datadir.clone().resolve_datadir(self.env.chain.chain());
84 let db_path = data_dir.db();
85 let static_files_path = data_dir.static_files();
86 let exex_wal_path = data_dir.exex_wal();
87
88 eyre::ensure!(
90 data_dir.data_dir().is_dir(),
91 "Datadir does not exist: {:?}",
92 data_dir.data_dir()
93 );
94
95 eyre::ensure!(db_path.is_dir(), "Database does not exist: {:?}", db_path);
97
98 match self.command {
99 Subcommands::Stats(command) => {
101 let access_rights = if command.skip_consistency_checks {
102 AccessRights::RoInconsistent
103 } else {
104 AccessRights::RO
105 };
106 db_exec!(self.env, tool, N, access_rights, {
107 command.execute(data_dir, &tool)?;
108 });
109 }
110 Subcommands::List(command) => {
111 db_exec!(self.env, tool, N, AccessRights::RO, {
112 command.execute(&tool)?;
113 });
114 }
115 Subcommands::Checksum(command) => {
116 db_exec!(self.env, tool, N, AccessRights::RO, {
117 command.execute(&tool)?;
118 });
119 }
120 Subcommands::Diff(command) => {
121 db_exec!(self.env, tool, N, AccessRights::RO, {
122 command.execute(&tool)?;
123 });
124 }
125 Subcommands::Get(command) => {
126 db_exec!(self.env, tool, N, AccessRights::RO, {
127 command.execute(&tool)?;
128 });
129 }
130 Subcommands::Drop { force } => {
131 if !force {
132 print!(
134 "Are you sure you want to drop the database at {data_dir}? This cannot be undone. (y/N): "
135 );
136 io::stdout().flush().unwrap();
138
139 let mut input = String::new();
140 io::stdin().read_line(&mut input).expect("Failed to read line");
141
142 if !input.trim().eq_ignore_ascii_case("y") {
143 println!("Database drop aborted!");
144 return Ok(())
145 }
146 }
147
148 db_exec!(self.env, tool, N, AccessRights::RW, {
149 tool.drop(db_path, static_files_path, exex_wal_path)?;
150 });
151 }
152 Subcommands::Clear(command) => {
153 db_exec!(self.env, tool, N, AccessRights::RW, {
154 command.execute(&tool)?;
155 });
156 }
157 Subcommands::RepairTrie(command) => {
158 let access_rights =
159 if command.dry_run { AccessRights::RO } else { AccessRights::RW };
160 db_exec!(self.env, tool, N, access_rights, {
161 command.execute(&tool)?;
162 });
163 }
164 Subcommands::StaticFileHeader(command) => {
165 db_exec!(self.env, tool, N, AccessRights::RoInconsistent, {
166 command.execute(&tool)?;
167 });
168 }
169 Subcommands::Version => {
170 let local_db_version = match get_db_version(&db_path) {
171 Ok(version) => Some(version),
172 Err(DatabaseVersionError::MissingFile) => None,
173 Err(err) => return Err(err.into()),
174 };
175
176 println!("Current database version: {DB_VERSION}");
177
178 if let Some(version) = local_db_version {
179 println!("Local database version: {version}");
180 } else {
181 println!("Local database is uninitialized");
182 }
183 }
184 Subcommands::Path => {
185 println!("{}", db_path.display());
186 }
187 Subcommands::Settings(command) => {
188 db_exec!(self.env, tool, N, command.access_rights(), {
189 command.execute(&tool)?;
190 });
191 }
192 Subcommands::AccountStorage(command) => {
193 db_exec!(self.env, tool, N, AccessRights::RO, {
194 command.execute(&tool)?;
195 });
196 }
197 }
198
199 Ok(())
200 }
201}
202
203impl<C: ChainSpecParser> Command<C> {
204 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
206 Some(&self.env.chain)
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
214 use std::path::Path;
215
216 #[test]
217 fn parse_stats_globals() {
218 let path = format!("../{}", SUPPORTED_CHAINS[0]);
219 let cmd = Command::<EthereumChainSpecParser>::try_parse_from([
220 "reth",
221 "--datadir",
222 &path,
223 "stats",
224 ])
225 .unwrap();
226 assert_eq!(cmd.env.datadir.resolve_datadir(cmd.env.chain.chain).as_ref(), Path::new(&path));
227 }
228}