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 checksum;
12mod clear;
13mod diff;
14mod get;
15mod list;
16mod repair_trie;
17mod stats;
18/// DB List TUI
19mod tui;
20
21/// `reth db` command
22#[derive(Debug, Parser)]
23pub struct Command<C: ChainSpecParser> {
24    #[command(flatten)]
25    env: EnvironmentArgs<C>,
26
27    #[command(subcommand)]
28    command: Subcommands,
29}
30
31#[derive(Subcommand, Debug)]
32/// `reth db` subcommands
33pub enum Subcommands {
34    /// Lists all the tables, their entry count and their size
35    Stats(stats::Command),
36    /// Lists the contents of a table
37    List(list::Command),
38    /// Calculates the content checksum of a table
39    Checksum(checksum::Command),
40    /// Create a diff between two database tables or two entire databases.
41    Diff(diff::Command),
42    /// Gets the content of a table for the given key
43    Get(get::Command),
44    /// Deletes all database entries
45    Drop {
46        /// Bypasses the interactive confirmation and drops the database directly
47        #[arg(short, long)]
48        force: bool,
49    },
50    /// Deletes all table entries
51    Clear(clear::Command),
52    /// Verifies trie consistency and outputs any inconsistencies
53    RepairTrie(repair_trie::Command),
54    /// Lists current and local database versions
55    Version,
56    /// Returns the full database path
57    Path,
58}
59
60/// `db_ro_exec` opens a database in read-only mode, and then execute with the provided command
61macro_rules! db_ro_exec {
62    ($env:expr, $tool:ident, $N:ident, $command:block) => {
63        let Environment { provider_factory, .. } = $env.init::<$N>(AccessRights::RO)?;
64
65        let $tool = DbTool::new(provider_factory)?;
66        $command;
67    };
68}
69
70impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
71    /// Execute `db` command
72    pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
73        let data_dir = self.env.datadir.clone().resolve_datadir(self.env.chain.chain());
74        let db_path = data_dir.db();
75        let static_files_path = data_dir.static_files();
76        let exex_wal_path = data_dir.exex_wal();
77
78        // ensure the provided datadir exist
79        eyre::ensure!(
80            data_dir.data_dir().is_dir(),
81            "Datadir does not exist: {:?}",
82            data_dir.data_dir()
83        );
84
85        // ensure the provided database exist
86        eyre::ensure!(db_path.is_dir(), "Database does not exist: {:?}", db_path);
87
88        match self.command {
89            // TODO: We'll need to add this on the DB trait.
90            Subcommands::Stats(command) => {
91                db_ro_exec!(self.env, tool, N, {
92                    command.execute(data_dir, &tool)?;
93                });
94            }
95            Subcommands::List(command) => {
96                db_ro_exec!(self.env, tool, N, {
97                    command.execute(&tool)?;
98                });
99            }
100            Subcommands::Checksum(command) => {
101                db_ro_exec!(self.env, tool, N, {
102                    command.execute(&tool)?;
103                });
104            }
105            Subcommands::Diff(command) => {
106                db_ro_exec!(self.env, tool, N, {
107                    command.execute(&tool)?;
108                });
109            }
110            Subcommands::Get(command) => {
111                db_ro_exec!(self.env, tool, N, {
112                    command.execute(&tool)?;
113                });
114            }
115            Subcommands::Drop { force } => {
116                if !force {
117                    // Ask for confirmation
118                    print!(
119                        "Are you sure you want to drop the database at {data_dir}? This cannot be undone. (y/N): "
120                    );
121                    // Flush the buffer to ensure the message is printed immediately
122                    io::stdout().flush().unwrap();
123
124                    let mut input = String::new();
125                    io::stdin().read_line(&mut input).expect("Failed to read line");
126
127                    if !input.trim().eq_ignore_ascii_case("y") {
128                        println!("Database drop aborted!");
129                        return Ok(())
130                    }
131                }
132
133                let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
134                let tool = DbTool::new(provider_factory)?;
135                tool.drop(db_path, static_files_path, exex_wal_path)?;
136            }
137            Subcommands::Clear(command) => {
138                let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
139                command.execute(provider_factory)?;
140            }
141            Subcommands::RepairTrie(command) => {
142                let access_rights =
143                    if command.dry_run { AccessRights::RO } else { AccessRights::RW };
144                let Environment { provider_factory, .. } = self.env.init::<N>(access_rights)?;
145                command.execute(provider_factory)?;
146            }
147            Subcommands::Version => {
148                let local_db_version = match get_db_version(&db_path) {
149                    Ok(version) => Some(version),
150                    Err(DatabaseVersionError::MissingFile) => None,
151                    Err(err) => return Err(err.into()),
152                };
153
154                println!("Current database version: {DB_VERSION}");
155
156                if let Some(version) = local_db_version {
157                    println!("Local database version: {version}");
158                } else {
159                    println!("Local database is uninitialized");
160                }
161            }
162            Subcommands::Path => {
163                println!("{}", db_path.display());
164            }
165        }
166
167        Ok(())
168    }
169}
170
171impl<C: ChainSpecParser> Command<C> {
172    /// Returns the underlying chain being used to run this command
173    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
174        Some(&self.env.chain)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
182    use std::path::Path;
183
184    #[test]
185    fn parse_stats_globals() {
186        let path = format!("../{}", SUPPORTED_CHAINS[0]);
187        let cmd = Command::<EthereumChainSpecParser>::try_parse_from([
188            "reth",
189            "--datadir",
190            &path,
191            "stats",
192        ])
193        .unwrap();
194        assert_eq!(cmd.env.datadir.resolve_datadir(cmd.env.chain.chain).as_ref(), Path::new(&path));
195    }
196}