reth_cli_commands/
import_era.rs1use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
3use alloy_chains::{ChainKind, NamedChain};
4use clap::{Args, Parser};
5use eyre::eyre;
6use reqwest::{Client, Url};
7use reth_chainspec::{EthChainSpec, EthereumHardforks};
8use reth_cli::chainspec::ChainSpecParser;
9use reth_era_downloader::{read_dir, EraClient, EraStream, EraStreamConfig};
10use reth_era_utils as era;
11use reth_etl::Collector;
12use reth_fs_util as fs;
13use reth_node_core::version::version_metadata;
14use reth_provider::StaticFileProviderFactory;
15use reth_static_file_types::StaticFileSegment;
16use std::{path::PathBuf, sync::Arc};
17use tracing::info;
18
19#[derive(Debug, Parser)]
21pub struct ImportEraCommand<C: ChainSpecParser> {
22 #[command(flatten)]
23 env: EnvironmentArgs<C>,
24
25 #[clap(flatten)]
26 import: ImportArgs,
27}
28
29#[derive(Debug, Args)]
30#[group(required = false, multiple = false)]
31pub struct ImportArgs {
32 #[arg(long, value_name = "IMPORT_ERA_PATH", verbatim_doc_comment)]
36 path: Option<PathBuf>,
37
38 #[arg(long, value_name = "IMPORT_ERA_URL", verbatim_doc_comment)]
43 url: Option<Url>,
44}
45
46trait TryFromChain {
47 fn try_to_url(&self) -> eyre::Result<Url>;
48}
49
50impl TryFromChain for ChainKind {
51 fn try_to_url(&self) -> eyre::Result<Url> {
52 Ok(match self {
53 ChainKind::Named(NamedChain::Mainnet) => {
54 Url::parse("https://era.ithaca.xyz/era1/index.html").expect("URL should be valid")
55 }
56 ChainKind::Named(NamedChain::Sepolia) => {
57 Url::parse("https://era.ithaca.xyz/sepolia-era1/index.html")
58 .expect("URL should be valid")
59 }
60 chain => return Err(eyre!("No known host for ERA files on chain {chain:?}")),
61 })
62 }
63}
64
65impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportEraCommand<C> {
66 pub async fn execute<N>(self, runtime: reth_tasks::Runtime) -> eyre::Result<()>
68 where
69 N: CliNodeTypes<ChainSpec = C::ChainSpec>,
70 {
71 info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
72
73 let Environment { provider_factory, config, .. } =
74 self.env.init::<N>(AccessRights::RW, runtime)?;
75
76 let mut hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir);
77
78 let next_block = provider_factory
79 .static_file_provider()
80 .get_highest_static_file_block(StaticFileSegment::Headers)
81 .unwrap_or_default() +
82 1;
83
84 if let Some(path) = self.import.path {
85 let stream = read_dir(path, next_block)?;
86
87 era::import(stream, &provider_factory, &mut hash_collector)?;
88 } else {
89 let url = match self.import.url {
90 Some(url) => url,
91 None => self.env.chain.chain().kind().try_to_url()?,
92 };
93 let folder =
94 self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era");
95
96 fs::create_dir_all(&folder)?;
97
98 let config = EraStreamConfig::default().start_from(next_block);
99 let client = EraClient::new(Client::new(), url, folder);
100 let stream = EraStream::new(client, config);
101
102 era::import(stream, &provider_factory, &mut hash_collector)?;
103 }
104
105 Ok(())
106 }
107}
108
109impl<C: ChainSpecParser> ImportEraCommand<C> {
110 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
112 Some(&self.env.chain)
113 }
114}