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    ) -> eyre::Result<()>
51    where
52        N: CliNodeTypes<ChainSpec = C::ChainSpec>,
53        Comp: CliNodeComponents<N>,
54    {
55        info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
56
57        let Environment { provider_factory, config, .. } = self.env.init::<N>(AccessRights::RW)?;
58
59        let components = components(provider_factory.chain_spec());
60
61        info!(target: "reth::cli", "Starting import of {} file(s)", self.paths.len());
62
63        let import_config = ImportConfig {
64            no_state: self.no_state,
65            chunk_len: self.chunk_len,
66            fail_on_invalid_block: self.fail_on_invalid_block,
67        };
68
69        let executor = components.evm_config().clone();
70        let consensus = Arc::new(components.consensus().clone());
71
72        let mut total_imported_blocks = 0;
73        let mut total_imported_txns = 0;
74        let mut total_decoded_blocks = 0;
75        let mut total_decoded_txns = 0;
76
77        // Import each file sequentially
78        for (index, path) in self.paths.iter().enumerate() {
79            info!(target: "reth::cli", "Importing file {} of {}: {}", index + 1, self.paths.len(), path.display());
80
81            let result = import_blocks_from_file(
82                path,
83                import_config.clone(),
84                provider_factory.clone(),
85                &config,
86                executor.clone(),
87                consensus.clone(),
88            )
89            .await?;
90
91            total_imported_blocks += result.total_imported_blocks;
92            total_imported_txns += result.total_imported_txns;
93            total_decoded_blocks += result.total_decoded_blocks;
94            total_decoded_txns += result.total_decoded_txns;
95
96            // Check if we stopped due to an invalid block
97            if result.stopped_on_invalid_block {
98                info!(target: "reth::cli",
99                      "Stopped at last valid block {} due to invalid block {} in file: {}. Imported {} blocks, {} transactions",
100                      result.last_valid_block.unwrap_or(0),
101                      result.bad_block.unwrap_or(0),
102                      path.display(),
103                      result.total_imported_blocks,
104                      result.total_imported_txns);
105                // Stop importing further files and exit successfully
106                break;
107            }
108
109            if !result.is_successful() {
110                return Err(eyre::eyre!(
111                    "Chain was partially imported from file: {}. Imported {}/{} blocks, {}/{} transactions",
112                    path.display(),
113                    result.total_imported_blocks,
114                    result.total_decoded_blocks,
115                    result.total_imported_txns,
116                    result.total_decoded_txns
117                ));
118            }
119
120            info!(target: "reth::cli",
121                  "Successfully imported file {}: {} blocks, {} transactions",
122                  path.display(), result.total_imported_blocks, result.total_imported_txns);
123        }
124
125        info!(target: "reth::cli",
126              "Import complete. Total: {}/{} blocks, {}/{} transactions",
127              total_imported_blocks, total_decoded_blocks, total_imported_txns, total_decoded_txns);
128
129        Ok(())
130    }
131}
132
133impl<C: ChainSpecParser> ImportCommand<C> {
134    /// Returns the underlying chain being used to run this command
135    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
136        Some(&self.env.chain)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
144
145    #[test]
146    fn parse_common_import_command_chain_args() {
147        for chain in SUPPORTED_CHAINS {
148            let args: ImportCommand<EthereumChainSpecParser> =
149                ImportCommand::parse_from(["reth", "--chain", chain, "."]);
150            assert_eq!(
151                Ok(args.env.chain.chain),
152                chain.parse::<reth_chainspec::Chain>(),
153                "failed to parse chain {chain}"
154            );
155        }
156    }
157
158    #[test]
159    fn parse_import_command_with_multiple_paths() {
160        let args: ImportCommand<EthereumChainSpecParser> =
161            ImportCommand::parse_from(["reth", "file1.rlp", "file2.rlp", "file3.rlp"]);
162        assert_eq!(args.paths.len(), 3);
163        assert_eq!(args.paths[0], PathBuf::from("file1.rlp"));
164        assert_eq!(args.paths[1], PathBuf::from("file2.rlp"));
165        assert_eq!(args.paths[2], PathBuf::from("file3.rlp"));
166    }
167
168    #[test]
169    fn parse_import_command_with_fail_on_invalid_block() {
170        let args: ImportCommand<EthereumChainSpecParser> =
171            ImportCommand::parse_from(["reth", "--fail-on-invalid-block", "chain.rlp"]);
172        assert!(args.fail_on_invalid_block);
173        assert_eq!(args.paths.len(), 1);
174        assert_eq!(args.paths[0], PathBuf::from("chain.rlp"));
175    }
176
177    #[test]
178    fn parse_import_command_default_stops_on_invalid_block() {
179        let args: ImportCommand<EthereumChainSpecParser> =
180            ImportCommand::parse_from(["reth", "chain.rlp"]);
181        assert!(!args.fail_on_invalid_block);
182    }
183}