1use crate::common::{AccessRights, CliNodeComponents, CliNodeTypes, Environment, EnvironmentArgs};
6use alloy_eips::BlockHashOrNumber;
7use backon::{ConstantBuilder, Retryable};
8use clap::Parser;
9use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
10use reth_cli::chainspec::ChainSpecParser;
11use reth_cli_runner::CliContext;
12use reth_cli_util::get_secret_key;
13use reth_config::config::{HashingConfig, SenderRecoveryConfig, TransactionLookupConfig};
14use reth_downloaders::{
15 bodies::bodies::BodiesDownloaderBuilder,
16 headers::reverse_headers::ReverseHeadersDownloaderBuilder,
17};
18use reth_exex::ExExManagerHandle;
19use reth_network::BlockDownloaderProvider;
20use reth_node_builder::common::metrics_hooks;
21use reth_node_core::{
22 args::{NetworkArgs, StageEnum},
23 utils::get_single_header,
24 version::version_metadata,
25};
26use reth_node_metrics::{
27 chain::ChainSpecInfo,
28 server::{MetricServer, MetricServerConfig},
29 version::VersionInfo,
30};
31use reth_primitives_traits::FastInstant as Instant;
32use reth_provider::{
33 providers::BlockchainProvider, ChainSpecProvider, DBProvider, DatabaseProviderFactory,
34 StageCheckpointReader, StageCheckpointWriter,
35};
36use reth_stages::{
37 stages::{
38 AccountHashingStage, BodyStage, ExecutionStage, HeaderStage, IndexAccountHistoryStage,
39 IndexStorageHistoryStage, MerkleStage, SenderRecoveryStage, StorageHashingStage,
40 TransactionLookupStage,
41 },
42 ExecInput, ExecOutput, ExecutionStageThresholds, Stage, StageExt, UnwindInput, UnwindOutput,
43};
44use std::{net::SocketAddr, sync::Arc};
45use tokio::sync::watch;
46use tracing::*;
47
48const HEADER_FETCH_RETRIES: usize = 5;
50
51const HASHING_UNSUPPORTED: &str = "The `hashing` stage cannot be run directly, run `account-hashing` and `storage-hashing` instead.";
52
53#[derive(Debug, Parser)]
55pub struct Command<C: ChainSpecParser> {
56 #[command(flatten)]
57 env: EnvironmentArgs<C>,
58
59 #[arg(long, value_name = "SOCKET")]
63 metrics: Option<SocketAddr>,
64
65 #[arg(value_enum)]
67 stage: StageEnum,
68
69 #[arg(long)]
71 from: u64,
72
73 #[arg(long, short)]
75 to: u64,
76
77 #[arg(long, value_parser = clap::value_parser!(u64).range(1..))]
79 batch_size: Option<u64>,
80
81 #[arg(long, short)]
87 skip_unwind: bool,
88
89 #[arg(long, short)]
98 commit: bool,
99
100 #[arg(long)]
102 checkpoints: bool,
103
104 #[command(flatten)]
105 network: NetworkArgs,
106}
107
108impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>> Command<C> {
109 pub async fn execute<N, Comp, F>(self, ctx: CliContext, components: F) -> eyre::Result<()>
111 where
112 N: CliNodeTypes<ChainSpec = C::ChainSpec>,
113 Comp: CliNodeComponents<N>,
114 F: FnOnce(Arc<C::ChainSpec>) -> Comp,
115 {
116 self.validate()?;
117
118 if self.requires_commit() && !self.commit {
120 return Err(eyre::eyre!(
121 "The stage {} requires overwriting existing static files and must commit, but `--commit` was not provided. Please pass `--commit` and try again.",
122 self.stage.to_string()
123 ));
124 }
125
126 let _ = fdlimit::raise_fd_limit();
129
130 let runtime = ctx.task_executor.clone();
131 let Environment { provider_factory, config, data_dir } =
132 self.env.init::<N>(AccessRights::RW, ctx.task_executor.clone())?;
133
134 let mut provider_rw = provider_factory.database_provider_rw()?;
135 let components = components(provider_factory.chain_spec());
136
137 if let Some(listen_addr) = self.metrics {
138 let config = MetricServerConfig::new(
139 listen_addr,
140 VersionInfo {
141 version: version_metadata().cargo_pkg_version.as_ref(),
142 build_timestamp: version_metadata().vergen_build_timestamp.as_ref(),
143 cargo_features: version_metadata().vergen_cargo_features.as_ref(),
144 git_sha: version_metadata().vergen_git_sha.as_ref(),
145 target_triple: version_metadata().vergen_cargo_target_triple.as_ref(),
146 build_profile: version_metadata().build_profile_name.as_ref(),
147 },
148 ChainSpecInfo { name: provider_factory.chain_spec().chain().to_string() },
149 ctx.task_executor,
150 metrics_hooks(&provider_factory),
151 data_dir.pprof_dumps(),
152 );
153
154 MetricServer::new(config).serve().await?;
155 }
156
157 let batch_size = self.batch_size.unwrap_or(self.to - self.from + 1);
158
159 let etl_config = config.stages.etl.clone();
160 let prune_modes = config.prune.segments.clone();
161
162 let (mut exec_stage, mut unwind_stage): (Box<dyn Stage<_>>, Option<Box<dyn Stage<_>>>) =
163 match self.stage {
164 StageEnum::Headers => {
165 let consensus = Arc::new(components.consensus().clone());
166
167 let network_secret_path = self
168 .network
169 .p2p_secret_key
170 .clone()
171 .unwrap_or_else(|| data_dir.p2p_secret());
172 let p2p_secret_key = get_secret_key(&network_secret_path)?;
173
174 let default_peers_path = data_dir.known_peers();
175
176 let network = self
177 .network
178 .network_config::<N::NetworkPrimitives>(
179 &config,
180 provider_factory.chain_spec(),
181 p2p_secret_key,
182 default_peers_path,
183 runtime.clone(),
184 )
185 .build(BlockchainProvider::new(provider_factory.clone())?)
186 .start_network()
187 .await?;
188 let fetch_client = Arc::new(network.fetch_client().await?);
189
190 let tip = (|| {
192 get_single_header(fetch_client.clone(), BlockHashOrNumber::Number(self.to))
193 })
194 .retry(ConstantBuilder::default().with_max_times(HEADER_FETCH_RETRIES))
195 .notify(|err, _| {
196 warn!(target: "reth::cli", error = %err, "Error requesting header. Retrying...")
197 })
198 .await?;
199 let (_, rx) = watch::channel(tip.hash());
200 (
201 Box::new(HeaderStage::new(
202 provider_factory.clone(),
203 ReverseHeadersDownloaderBuilder::new(config.stages.headers)
204 .build(fetch_client, consensus.clone()),
205 rx,
206 etl_config,
207 )),
208 None,
209 )
210 }
211 StageEnum::Bodies => {
212 let consensus = Arc::new(components.consensus().clone());
213
214 let mut config = config;
215 config.peers.trusted_nodes_only |= self.network.trusted_only;
216 config.peers.trusted_nodes.extend(self.network.trusted_peers.clone());
217
218 let network_secret_path = self
219 .network
220 .p2p_secret_key
221 .clone()
222 .unwrap_or_else(|| data_dir.p2p_secret());
223 let p2p_secret_key = get_secret_key(&network_secret_path)?;
224
225 let default_peers_path = data_dir.known_peers();
226
227 let network = self
228 .network
229 .network_config::<N::NetworkPrimitives>(
230 &config,
231 provider_factory.chain_spec(),
232 p2p_secret_key,
233 default_peers_path,
234 runtime.clone(),
235 )
236 .build(BlockchainProvider::new(provider_factory.clone())?)
237 .start_network()
238 .await?;
239 let fetch_client = Arc::new(network.fetch_client().await?);
240
241 let stage = BodyStage::new(
242 BodiesDownloaderBuilder::default()
243 .with_stream_batch_size(batch_size as usize)
244 .with_request_limit(config.stages.bodies.downloader_request_limit)
245 .with_max_buffered_blocks_size_bytes(
246 config.stages.bodies.downloader_max_buffered_blocks_size_bytes,
247 )
248 .with_concurrent_requests_range(
249 config.stages.bodies.downloader_min_concurrent_requests..=
250 config.stages.bodies.downloader_max_concurrent_requests,
251 )
252 .build(fetch_client, consensus.clone(), provider_factory.clone()),
253 );
254 (Box::new(stage), None)
255 }
256 StageEnum::Senders => (
257 Box::new(SenderRecoveryStage::new(
258 SenderRecoveryConfig { commit_threshold: batch_size },
259 None,
260 )),
261 None,
262 ),
263 StageEnum::Execution => (
264 Box::new(ExecutionStage::new(
265 components.evm_config().clone(),
266 Arc::new(components.consensus().clone()),
267 ExecutionStageThresholds {
268 max_blocks: Some(batch_size),
269 max_changes: None,
270 max_cumulative_gas: None,
271 max_duration: None,
272 },
273 config.stages.merkle.incremental_threshold,
274 ExExManagerHandle::empty(),
275 )),
276 None,
277 ),
278 StageEnum::TxLookup => (
279 Box::new(TransactionLookupStage::new(
280 TransactionLookupConfig { chunk_size: batch_size },
281 etl_config,
282 prune_modes.transaction_lookup,
283 )),
284 None,
285 ),
286 StageEnum::AccountHashing => (
287 Box::new(AccountHashingStage::new(
288 HashingConfig {
289 clean_threshold: 1,
290 commit_threshold: batch_size,
291 commit_entries: u64::MAX,
292 },
293 etl_config,
294 )),
295 None,
296 ),
297 StageEnum::StorageHashing => (
298 Box::new(StorageHashingStage::new(
299 HashingConfig {
300 clean_threshold: 1,
301 commit_threshold: batch_size,
302 commit_entries: u64::MAX,
303 },
304 etl_config,
305 )),
306 None,
307 ),
308 StageEnum::Merkle => (
309 Box::new(MerkleStage::new_execution(
310 config.stages.merkle.rebuild_threshold,
311 config.stages.merkle.incremental_threshold,
312 )),
313 Some(Box::new(MerkleStage::default_unwind())),
314 ),
315 StageEnum::AccountHistory => (
316 Box::new(IndexAccountHistoryStage::new(
317 config.stages.index_account_history,
318 etl_config,
319 prune_modes.account_history,
320 )),
321 None,
322 ),
323 StageEnum::StorageHistory => (
324 Box::new(IndexStorageHistoryStage::new(
325 config.stages.index_storage_history,
326 etl_config,
327 prune_modes.storage_history,
328 )),
329 None,
330 ),
331 StageEnum::Hashing => eyre::bail!(HASHING_UNSUPPORTED),
332 };
333 let checkpoint = provider_rw.get_stage_checkpoint(exec_stage.id())?.unwrap_or_default();
334
335 let unwind_stage = unwind_stage.as_mut().unwrap_or(&mut exec_stage);
336
337 let mut unwind = UnwindInput {
338 checkpoint: checkpoint.with_block_number(self.to),
339 unwind_to: self.from,
340 bad_block: None,
341 };
342
343 if !self.skip_unwind {
344 while unwind.checkpoint.block_number > self.from {
345 let UnwindOutput { checkpoint } = unwind_stage.unwind(&provider_rw, unwind)?;
346 unwind.checkpoint = checkpoint;
347
348 if self.checkpoints {
349 provider_rw.save_stage_checkpoint(unwind_stage.id(), checkpoint)?;
350 }
351
352 if self.commit {
353 provider_rw.commit()?;
354 provider_rw = provider_factory.database_provider_rw()?;
355 }
356 }
357 }
358
359 let mut input = ExecInput {
360 target: Some(self.to),
361 checkpoint: Some(checkpoint.with_block_number(self.from)),
362 };
363
364 let start = Instant::now();
365 info!(target: "reth::cli", stage = %self.stage, "Executing stage");
366 loop {
367 exec_stage.execute_ready(input).await?;
368 let ExecOutput { checkpoint, done } = exec_stage.execute(&provider_rw, input)?;
369
370 input.checkpoint = Some(checkpoint);
371
372 if self.checkpoints {
373 provider_rw.save_stage_checkpoint(exec_stage.id(), checkpoint)?;
374 }
375 if self.commit {
376 provider_rw.commit()?;
377 provider_rw = provider_factory.database_provider_rw()?;
378 }
379
380 if done {
381 break
382 }
383 }
384 info!(target: "reth::cli", stage = %self.stage, time = ?start.elapsed(), "Finished stage");
385
386 Ok(())
387 }
388}
389
390impl<C: ChainSpecParser> Command<C> {
391 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
393 Some(&self.env.chain)
394 }
395
396 fn validate(&self) -> eyre::Result<()> {
398 if self.stage == StageEnum::Hashing {
399 eyre::bail!(HASHING_UNSUPPORTED);
400 }
401 eyre::ensure!(
402 self.from <= self.to,
403 "`--from` ({}) must not be greater than `--to` ({})",
404 self.from,
405 self.to
406 );
407 Ok(())
408 }
409
410 pub fn requires_commit(&self) -> bool {
416 matches!(self.stage, StageEnum::Headers | StageEnum::Bodies | StageEnum::Execution)
417 }
418}