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