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