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