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 ) -> 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 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 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 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 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}