1use 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#[derive(Debug, Parser)]
17pub struct ImportCommand<C: ChainSpecParser> {
18 #[command(flatten)]
19 env: EnvironmentArgs<C>,
20
21 #[arg(long, verbatim_doc_comment)]
23 no_state: bool,
24
25 #[arg(long, value_name = "CHUNK_LEN", verbatim_doc_comment)]
27 chunk_len: Option<u64>,
28
29 #[arg(long, verbatim_doc_comment)]
35 fail_on_invalid_block: bool,
36
37 #[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 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 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 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 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 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}