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    /// The path(s) to block file(s) for import.
30    ///
31    /// The online stages (headers and bodies) are replaced by a file import, after which the
32    /// remaining stages are executed. Multiple files will be imported sequentially.
33    #[arg(value_name = "IMPORT_PATH", required = true, num_args = 1.., verbatim_doc_comment)]
34    paths: Vec<PathBuf>,
35}
36
37impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportCommand<C> {
38    /// Execute `import` command
39    pub async fn execute<N, Comp>(
40        self,
41        components: impl FnOnce(Arc<N::ChainSpec>) -> Comp,
42    ) -> eyre::Result<()>
43    where
44        N: CliNodeTypes<ChainSpec = C::ChainSpec>,
45        Comp: CliNodeComponents<N>,
46    {
47        info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
48
49        let Environment { provider_factory, config, .. } = self.env.init::<N>(AccessRights::RW)?;
50
51        let components = components(provider_factory.chain_spec());
52
53        info!(target: "reth::cli", "Starting import of {} file(s)", self.paths.len());
54
55        let import_config = ImportConfig { no_state: self.no_state, chunk_len: self.chunk_len };
56
57        let executor = components.evm_config().clone();
58        let consensus = Arc::new(components.consensus().clone());
59
60        let mut total_imported_blocks = 0;
61        let mut total_imported_txns = 0;
62        let mut total_decoded_blocks = 0;
63        let mut total_decoded_txns = 0;
64
65        // Import each file sequentially
66        for (index, path) in self.paths.iter().enumerate() {
67            info!(target: "reth::cli", "Importing file {} of {}: {}", index + 1, self.paths.len(), path.display());
68
69            let result = import_blocks_from_file(
70                path,
71                import_config.clone(),
72                provider_factory.clone(),
73                &config,
74                executor.clone(),
75                consensus.clone(),
76            )
77            .await?;
78
79            total_imported_blocks += result.total_imported_blocks;
80            total_imported_txns += result.total_imported_txns;
81            total_decoded_blocks += result.total_decoded_blocks;
82            total_decoded_txns += result.total_decoded_txns;
83
84            if !result.is_complete() {
85                return Err(eyre::eyre!(
86                    "Chain was partially imported from file: {}. Imported {}/{} blocks, {}/{} transactions",
87                    path.display(),
88                    result.total_imported_blocks,
89                    result.total_decoded_blocks,
90                    result.total_imported_txns,
91                    result.total_decoded_txns
92                ));
93            }
94
95            info!(target: "reth::cli",
96                  "Successfully imported file {}: {} blocks, {} transactions",
97                  path.display(), result.total_imported_blocks, result.total_imported_txns);
98        }
99
100        info!(target: "reth::cli",
101              "All files imported successfully. Total: {}/{} blocks, {}/{} transactions",
102              total_imported_blocks, total_decoded_blocks, total_imported_txns, total_decoded_txns);
103
104        Ok(())
105    }
106}
107
108impl<C: ChainSpecParser> ImportCommand<C> {
109    /// Returns the underlying chain being used to run this command
110    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
111        Some(&self.env.chain)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
119
120    #[test]
121    fn parse_common_import_command_chain_args() {
122        for chain in SUPPORTED_CHAINS {
123            let args: ImportCommand<EthereumChainSpecParser> =
124                ImportCommand::parse_from(["reth", "--chain", chain, "."]);
125            assert_eq!(
126                Ok(args.env.chain.chain),
127                chain.parse::<reth_chainspec::Chain>(),
128                "failed to parse chain {chain}"
129            );
130        }
131    }
132
133    #[test]
134    fn parse_import_command_with_multiple_paths() {
135        let args: ImportCommand<EthereumChainSpecParser> =
136            ImportCommand::parse_from(["reth", "file1.rlp", "file2.rlp", "file3.rlp"]);
137        assert_eq!(args.paths.len(), 3);
138        assert_eq!(args.paths[0], PathBuf::from("file1.rlp"));
139        assert_eq!(args.paths[1], PathBuf::from("file2.rlp"));
140        assert_eq!(args.paths[2], PathBuf::from("file3.rlp"));
141    }
142}