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 migrate_v2;
20mod prune_checkpoints;
21mod repair_trie;
22mod settings;
23mod stage_checkpoints;
24mod state;
25mod static_file_header;
26mod stats;
27/// DB List TUI
28mod tui;
29
30/// `reth db` command
31#[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)]
41/// `reth db` subcommands
42pub enum Subcommands {
43    /// Lists all the tables, their entry count and their size
44    Stats(stats::Command),
45    /// Lists the contents of a table
46    List(list::Command),
47    /// Calculates the content checksum of a table or static file segment
48    Checksum(checksum::Command),
49    /// Copies the MDBX database to a new location (bundled mdbx_copy)
50    Copy(copy::Command),
51    /// Create a diff between two database tables or two entire databases.
52    Diff(diff::Command),
53    /// Gets the content of a table for the given key
54    Get(get::Command),
55    /// Deletes all database entries
56    Drop {
57        /// Bypasses the interactive confirmation and drops the database directly
58        #[arg(short, long)]
59        force: bool,
60    },
61    /// Deletes all table entries
62    Clear(clear::Command),
63    /// Verifies trie consistency and outputs any inconsistencies
64    RepairTrie(repair_trie::Command),
65    /// Reads and displays the static file segment header
66    StaticFileHeader(static_file_header::Command),
67    /// Lists current and local database versions
68    Version,
69    /// Returns the full database path
70    Path,
71    /// Manage storage settings
72    Settings(settings::Command),
73    /// View or set prune checkpoints
74    PruneCheckpoints(prune_checkpoints::Command),
75    // View or set stage checkpoints
76    StageCheckpoints(stage_checkpoints::Command),
77    /// Gets storage size information for an account
78    AccountStorage(account_storage::Command),
79    /// Gets account state and storage at a specific block
80    State(state::Command),
81    /// Migrate storage layout from v1 (MDBX-only) to v2 (static files + RocksDB)
82    #[command(name = "migrate-v2")]
83    MigrateV2(migrate_v2::Command),
84}
85
86impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
87    /// Execute `db` command
88    pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(
89        self,
90        ctx: CliContext,
91    ) -> eyre::Result<()> {
92        /// Initializes a provider factory with specified access rights, and then executes the
93        /// provided command.
94        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        // ensure the provided datadir exists
110        eyre::ensure!(
111            data_dir.data_dir().is_dir(),
112            "Datadir does not exist: {:?}",
113            data_dir.data_dir()
114        );
115
116        // ensure the provided database exists
117        eyre::ensure!(db_path.is_dir(), "Database does not exist: {:?}", db_path);
118
119        match self.command {
120            // TODO: We'll need to add this on the DB trait.
121            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                    // Ask for confirmation
159                    print!(
160                        "Are you sure you want to drop the database at {data_dir}? This cannot be undone. (y/N): "
161                    );
162                    // Flush the buffer to ensure the message is printed immediately
163                    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 access_rights =
185                    if command.dry_run { AccessRights::RO } else { AccessRights::RW };
186                db_exec!(self.env, tool, N, access_rights, {
187                    command.execute(&tool, ctx.task_executor, &data_dir)?;
188                });
189            }
190            Subcommands::StaticFileHeader(command) => {
191                db_exec!(self.env, tool, N, AccessRights::RoInconsistent, {
192                    command.execute(&tool)?;
193                });
194            }
195            Subcommands::Version => {
196                let local_db_version = match get_db_version(&db_path) {
197                    Ok(version) => Some(version),
198                    Err(DatabaseVersionError::MissingFile) => None,
199                    Err(err) => return Err(err.into()),
200                };
201
202                println!("Current database version: {DB_VERSION}");
203
204                if let Some(version) = local_db_version {
205                    println!("Local database version: {version}");
206                } else {
207                    println!("Local database is uninitialized");
208                }
209            }
210            Subcommands::Path => {
211                println!("{}", db_path.display());
212            }
213            Subcommands::Settings(command) => {
214                db_exec!(self.env, tool, N, command.access_rights(), {
215                    command.execute(&tool)?;
216                });
217            }
218            Subcommands::PruneCheckpoints(command) => {
219                db_exec!(self.env, tool, N, command.access_rights(), {
220                    command.execute(&tool)?;
221                });
222            }
223            Subcommands::StageCheckpoints(command) => {
224                db_exec!(self.env, tool, N, command.access_rights(), {
225                    command.execute(&tool)?;
226                });
227            }
228            Subcommands::AccountStorage(command) => {
229                db_exec!(self.env, tool, N, AccessRights::RO, {
230                    command.execute(&tool)?;
231                });
232            }
233            Subcommands::State(command) => {
234                db_exec!(self.env, tool, N, AccessRights::RO, {
235                    command.execute(&tool)?;
236                });
237            }
238            Subcommands::MigrateV2(command) => {
239                let Environment { provider_factory, .. } =
240                    self.env.init::<N>(AccessRights::RW, ctx.task_executor.clone())?;
241
242                // Migrate changesets+receipts, clear tables, compact MDBX
243                command.execute::<N>(provider_factory).await?;
244            }
245        }
246
247        Ok(())
248    }
249}
250
251impl<C: ChainSpecParser> Command<C> {
252    /// Returns the underlying chain being used to run this command
253    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
254        Some(&self.env.chain)
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
262    use std::path::Path;
263
264    #[test]
265    fn parse_stats_globals() {
266        let path = format!("../{}", SUPPORTED_CHAINS[0]);
267        let cmd = Command::<EthereumChainSpecParser>::try_parse_from([
268            "reth",
269            "--datadir",
270            &path,
271            "stats",
272        ])
273        .unwrap();
274        assert_eq!(cmd.env.datadir.resolve_datadir(cmd.env.chain.chain).as_ref(), Path::new(&path));
275    }
276}