Skip to main content

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