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