reth_cli_commands/db/
mod.rs

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;
21/// DB List TUI
22mod tui;
23
24/// `reth db` command
25#[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)]
35/// `reth db` subcommands
36pub enum Subcommands {
37    /// Lists all the tables, their entry count and their size
38    Stats(stats::Command),
39    /// Lists the contents of a table
40    List(list::Command),
41    /// Calculates the content checksum of a table
42    Checksum(checksum::Command),
43    /// Create a diff between two database tables or two entire databases.
44    Diff(diff::Command),
45    /// Gets the content of a table for the given key
46    Get(get::Command),
47    /// Deletes all database entries
48    Drop {
49        /// Bypasses the interactive confirmation and drops the database directly
50        #[arg(short, long)]
51        force: bool,
52    },
53    /// Deletes all table entries
54    Clear(clear::Command),
55    /// Verifies trie consistency and outputs any inconsistencies
56    RepairTrie(repair_trie::Command),
57    /// Reads and displays the static file segment header
58    StaticFileHeader(static_file_header::Command),
59    /// Lists current and local database versions
60    Version,
61    /// Returns the full database path
62    Path,
63    /// Manage storage settings
64    Settings(settings::Command),
65    /// Gets storage size information for an account
66    AccountStorage(account_storage::Command),
67}
68
69/// Initializes a provider factory with specified access rights, and then execute with the provided
70/// command
71macro_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    /// Execute `db` command
82    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        // ensure the provided datadir exist
89        eyre::ensure!(
90            data_dir.data_dir().is_dir(),
91            "Datadir does not exist: {:?}",
92            data_dir.data_dir()
93        );
94
95        // ensure the provided database exist
96        eyre::ensure!(db_path.is_dir(), "Database does not exist: {:?}", db_path);
97
98        match self.command {
99            // TODO: We'll need to add this on the DB trait.
100            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                    // Ask for confirmation
133                    print!(
134                        "Are you sure you want to drop the database at {data_dir}? This cannot be undone. (y/N): "
135                    );
136                    // Flush the buffer to ensure the message is printed immediately
137                    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    /// Returns the underlying chain being used to run this command
205    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}