Skip to main content

reth_cli_commands/
import.rs

1//! Command that initializes the node by importing a chain from a file.
2use crate::{
3    common::{AccessRights, CliNodeComponents, CliNodeTypes, Environment, EnvironmentArgs},
4    import_core::{import_blocks_from_file, ImportConfig},
5};
6use clap::Parser;
7use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
8use reth_cli::chainspec::ChainSpecParser;
9use reth_node_core::version::version_metadata;
10use std::{path::PathBuf, sync::Arc};
11use tracing::info;
12
13pub use crate::import_core::build_import_pipeline_impl as build_import_pipeline;
14
15/// Syncs RLP encoded blocks from a file or files.
16#[derive(Debug, Parser)]
17pub struct ImportCommand<C: ChainSpecParser> {
18    #[command(flatten)]
19    env: EnvironmentArgs<C>,
20
21    /// Disables stages that require state.
22    #[arg(long, verbatim_doc_comment)]
23    no_state: bool,
24
25    /// Chunk byte length to read from file.
26    #[arg(long, value_name = "CHUNK_LEN", verbatim_doc_comment)]
27    chunk_len: Option<u64>,
28
29    /// Fail immediately when an invalid block is encountered.
30    ///
31    /// By default, the import will stop at the last valid block if an invalid block is
32    /// encountered during execution or validation, leaving the database at the last valid
33    /// block state. When this flag is set, the import will instead fail with an error.
34    #[arg(long, verbatim_doc_comment)]
35    fail_on_invalid_block: bool,
36
37    /// The path(s) to block file(s) for import.
38    ///
39    /// The online stages (headers and bodies) are replaced by a file import, after which the
40    /// remaining stages are executed. Multiple files will be imported sequentially.
41    #[arg(value_name = "IMPORT_PATH", required = true, num_args = 1.., verbatim_doc_comment)]
42    paths: Vec<PathBuf>,
43}
44
45impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportCommand<C> {
46    /// Execute `import` command
47    pub async fn execute<N, Comp>(
48        self,
49        components: impl FnOnce(Arc<N::ChainSpec>) -> Comp,
50        runtime: reth_tasks::Runtime,
51    ) -> eyre::Result<()>
52    where
53        N: CliNodeTypes<ChainSpec = C::ChainSpec>,
54        Comp: CliNodeComponents<N>,
55    {
56        info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
57
58        let Environment { provider_factory, config, .. } =
59            self.env.init::<N>(AccessRights::RW, runtime.clone())?;
60
61        let components = components(provider_factory.chain_spec());
62
63        info!(target: "reth::cli", "Starting import of {} file(s)", self.paths.len());
64
65        let import_config = ImportConfig {
66            no_state: self.no_state,
67            chunk_len: self.chunk_len,
68            fail_on_invalid_block: self.fail_on_invalid_block,
69        };
70
71        let executor = components.evm_config().clone();
72        let consensus = Arc::new(components.consensus().clone());
73
74        let mut total_imported_blocks = 0;
75        let mut total_imported_txns = 0;
76        let mut total_decoded_blocks = 0;
77        let mut total_decoded_txns = 0;
78
79        // Import each file sequentially
80        for (index, path) in self.paths.iter().enumerate() {
81            info!(target: "reth::cli", "Importing file {} of {}: {}", index + 1, self.paths.len(), path.display());
82
83            let result = import_blocks_from_file(
84                path,
85                import_config.clone(),
86                provider_factory.clone(),
87                &config,
88                executor.clone(),
89                consensus.clone(),
90                runtime.clone(),
91            )
92            .await?;
93
94            total_imported_blocks += result.total_imported_blocks;
95            total_imported_txns += result.total_imported_txns;
96            total_decoded_blocks += result.total_decoded_blocks;
97            total_decoded_txns += result.total_decoded_txns;
98
99            // Check if we stopped due to an invalid block
100            if result.stopped_on_invalid_block {
101                info!(target: "reth::cli",
102                      "Stopped at last valid block {} due to invalid block {} in file: {}. Imported {} blocks, {} transactions",
103                      result.last_valid_block.unwrap_or(0),
104                      result.bad_block.unwrap_or(0),
105                      path.display(),
106                      result.total_imported_blocks,
107                      result.total_imported_txns);
108                // Stop importing further files and exit successfully
109                break;
110            }
111
112            if !result.is_successful() {
113                return Err(eyre::eyre!(
114                    "Chain was partially imported from file: {}. Imported {}/{} blocks, {}/{} transactions",
115                    path.display(),
116                    result.total_imported_blocks,
117                    result.total_decoded_blocks,
118                    result.total_imported_txns,
119                    result.total_decoded_txns
120                ));
121            }
122
123            info!(target: "reth::cli",
124                  "Successfully imported file {}: {} blocks, {} transactions",
125                  path.display(), result.total_imported_blocks, result.total_imported_txns);
126        }
127
128        info!(target: "reth::cli",
129              "Import complete. Total: {}/{} blocks, {}/{} transactions",
130              total_imported_blocks, total_decoded_blocks, total_imported_txns, total_decoded_txns);
131
132        Ok(())
133    }
134}
135
136impl<C: ChainSpecParser> ImportCommand<C> {
137    /// Returns the underlying chain being used to run this command
138    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
139        Some(&self.env.chain)
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
147
148    #[test]
149    fn parse_common_import_command_chain_args() {
150        for chain in SUPPORTED_CHAINS {
151            let args: ImportCommand<EthereumChainSpecParser> =
152                ImportCommand::parse_from(["reth", "--chain", chain, "."]);
153            assert_eq!(
154                Ok(args.env.chain.chain),
155                chain.parse::<reth_chainspec::Chain>(),
156                "failed to parse chain {chain}"
157            );
158        }
159    }
160
161    #[test]
162    fn parse_import_command_with_multiple_paths() {
163        let args: ImportCommand<EthereumChainSpecParser> =
164            ImportCommand::parse_from(["reth", "file1.rlp", "file2.rlp", "file3.rlp"]);
165        assert_eq!(args.paths.len(), 3);
166        assert_eq!(args.paths[0], PathBuf::from("file1.rlp"));
167        assert_eq!(args.paths[1], PathBuf::from("file2.rlp"));
168        assert_eq!(args.paths[2], PathBuf::from("file3.rlp"));
169    }
170
171    #[test]
172    fn parse_import_command_with_fail_on_invalid_block() {
173        let args: ImportCommand<EthereumChainSpecParser> =
174            ImportCommand::parse_from(["reth", "--fail-on-invalid-block", "chain.rlp"]);
175        assert!(args.fail_on_invalid_block);
176        assert_eq!(args.paths.len(), 1);
177        assert_eq!(args.paths[0], PathBuf::from("chain.rlp"));
178    }
179
180    #[test]
181    fn parse_import_command_default_stops_on_invalid_block() {
182        let args: ImportCommand<EthereumChainSpecParser> =
183            ImportCommand::parse_from(["reth", "chain.rlp"]);
184        assert!(!args.fail_on_invalid_block);
185    }
186}