Skip to main content

reth_cli_commands/stage/dump/
mod.rs

1//! Database debugging tool
2use crate::common::{AccessRights, CliNodeComponents, CliNodeTypes, Environment, EnvironmentArgs};
3use clap::Parser;
4use reth_chainspec::{EthChainSpec, EthereumHardforks};
5use reth_cli::chainspec::ChainSpecParser;
6use reth_db::{init_db, mdbx::DatabaseArguments, DatabaseEnv};
7use reth_db_api::{
8    cursor::DbCursorRO, database::Database, models::ClientVersion, table::TableImporter, tables,
9    transaction::DbTx,
10};
11use reth_db_common::DbTool;
12use reth_node_builder::NodeTypesWithDB;
13use reth_node_core::{
14    args::DatadirArgs,
15    dirs::{DataDirPath, PlatformPath},
16};
17use std::{path::PathBuf, sync::Arc};
18use tracing::info;
19
20mod hashing_storage;
21use hashing_storage::dump_hashing_storage_stage;
22
23mod hashing_account;
24use hashing_account::dump_hashing_account_stage;
25
26mod execution;
27use execution::dump_execution_stage;
28
29mod merkle;
30use merkle::dump_merkle_stage;
31
32/// `reth dump-stage` command.
33///
34/// Note: mutates the source datadir (unwinds hashing/merkle/execution before copying tables).
35/// Stop the node and back up the datadir first.
36#[derive(Debug, Parser)]
37pub struct Command<C: ChainSpecParser> {
38    #[command(flatten)]
39    env: EnvironmentArgs<C>,
40
41    #[command(subcommand)]
42    command: Stages,
43}
44
45/// Supported stages to be dumped
46#[derive(Debug, Clone, Parser)]
47pub enum Stages {
48    /// Execution stage.
49    Execution(StageCommand),
50    /// `StorageHashing` stage.
51    StorageHashing(StageCommand),
52    /// `AccountHashing` stage.
53    AccountHashing(StageCommand),
54    /// Merkle stage.
55    Merkle(StageCommand),
56}
57
58/// Stage command that takes a range
59#[derive(Debug, Clone, Parser)]
60pub struct StageCommand {
61    /// The path to the new datadir folder.
62    #[arg(long, value_name = "OUTPUT_PATH", verbatim_doc_comment)]
63    output_datadir: PlatformPath<DataDirPath>,
64
65    /// From which block.
66    #[arg(long, short)]
67    from: u64,
68    /// To which block.
69    #[arg(long, short)]
70    to: u64,
71    /// If passed, it will dry-run a stage execution from the newly created database right after
72    /// dumping.
73    #[arg(long, short, default_value = "false")]
74    dry_run: bool,
75}
76
77impl StageCommand {
78    /// Validates the block range before any database is opened.
79    fn validate(&self) -> eyre::Result<()> {
80        eyre::ensure!(self.from > 0, "`--from` must be at least 1");
81        eyre::ensure!(
82            self.from < self.to,
83            "`--from` ({}) must be lower than `--to` ({})",
84            self.from,
85            self.to
86        );
87        Ok(())
88    }
89}
90
91macro_rules! handle_stage {
92    ($stage_fn:ident, $tool:expr, $command:expr, $runtime:expr) => {{
93        let StageCommand { output_datadir, from, to, dry_run, .. } = $command;
94        let output_datadir =
95            output_datadir.with_chain($tool.chain().chain(), DatadirArgs::default());
96        $stage_fn($tool, *from, *to, output_datadir, *dry_run, $runtime).await?
97    }};
98
99    ($stage_fn:ident, $tool:expr, $command:expr, $executor:expr, $consensus:expr, $runtime:expr) => {{
100        let StageCommand { output_datadir, from, to, dry_run, .. } = $command;
101        let output_datadir =
102            output_datadir.with_chain($tool.chain().chain(), DatadirArgs::default());
103        $stage_fn($tool, *from, *to, output_datadir, *dry_run, $executor, $consensus, $runtime)
104            .await?
105    }};
106}
107
108impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
109    /// Execute `dump-stage` command
110    pub async fn execute<N, Comp, F>(
111        self,
112        components: F,
113        runtime: reth_tasks::Runtime,
114    ) -> eyre::Result<()>
115    where
116        N: CliNodeTypes<ChainSpec = C::ChainSpec>,
117        Comp: CliNodeComponents<N>,
118        F: FnOnce(Arc<C::ChainSpec>) -> Comp,
119    {
120        match &self.command {
121            Stages::Execution(cmd) |
122            Stages::StorageHashing(cmd) |
123            Stages::AccountHashing(cmd) |
124            Stages::Merkle(cmd) => cmd.validate()?,
125        }
126
127        // `unwind_and_copy` opens a RW provider on the source datadir, so open RW here.
128        let Environment { provider_factory, .. } =
129            self.env.init::<N>(AccessRights::RW, runtime.clone())?;
130        let tool = DbTool::new(provider_factory)?;
131        let components = components(tool.chain());
132        let evm_config = components.evm_config().clone();
133        let consensus = components.consensus().clone();
134
135        match &self.command {
136            Stages::Execution(cmd) => {
137                handle_stage!(
138                    dump_execution_stage,
139                    &tool,
140                    cmd,
141                    evm_config,
142                    consensus,
143                    runtime.clone()
144                )
145            }
146            Stages::StorageHashing(cmd) => {
147                handle_stage!(dump_hashing_storage_stage, &tool, cmd, runtime.clone())
148            }
149            Stages::AccountHashing(cmd) => {
150                handle_stage!(dump_hashing_account_stage, &tool, cmd, runtime.clone())
151            }
152            Stages::Merkle(cmd) => {
153                handle_stage!(dump_merkle_stage, &tool, cmd, evm_config, consensus, runtime.clone())
154            }
155        }
156
157        Ok(())
158    }
159}
160
161impl<C: ChainSpecParser> Command<C> {
162    /// Returns the underlying chain being used to run this command
163    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
164        Some(&self.env.chain)
165    }
166}
167
168/// Sets up the database and initial state on [`tables::BlockBodyIndices`]. Also returns the tip
169/// block number.
170pub(crate) fn setup<N: NodeTypesWithDB>(
171    from: u64,
172    to: u64,
173    output_db: &PathBuf,
174    db_tool: &DbTool<N>,
175) -> eyre::Result<(DatabaseEnv, u64)> {
176    info!(target: "reth::cli", ?output_db, "Creating separate db");
177
178    let output_datadir = init_db(output_db, DatabaseArguments::new(ClientVersion::default()))?;
179
180    output_datadir.update(|tx| {
181        tx.import_table_with_range::<tables::BlockBodyIndices, _>(
182            &db_tool.provider_factory.db_ref().tx()?,
183            Some(from - 1),
184            to + 1,
185        )
186    })??;
187
188    let (tip_block_number, _) = db_tool
189        .provider_factory
190        .db_ref()
191        .view(|tx| tx.cursor_read::<tables::BlockBodyIndices>()?.last())??
192        .ok_or_else(|| eyre::eyre!("`BlockBodyIndices` table of the source database is empty"))?;
193
194    Ok((output_datadir, tip_block_number))
195}