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