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