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) -> 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, .. } = self.env.init::<N>(AccessRights::RW)?;
74
75 let mut hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir);
76
77 let next_block = provider_factory
78 .static_file_provider()
79 .get_highest_static_file_block(StaticFileSegment::Headers)
80 .unwrap_or_default() +
81 1;
82
83 if let Some(path) = self.import.path {
84 let stream = read_dir(path, next_block)?;
85
86 era::import(stream, &provider_factory, &mut hash_collector)?;
87 } else {
88 let url = match self.import.url {
89 Some(url) => url,
90 None => self.env.chain.chain().kind().try_to_url()?,
91 };
92 let folder =
93 self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era");
94
95 fs::create_dir_all(&folder)?;
96
97 let config = EraStreamConfig::default().start_from(next_block);
98 let client = EraClient::new(Client::new(), url, folder);
99 let stream = EraStream::new(client, config);
100
101 era::import(stream, &provider_factory, &mut hash_collector)?;
102 }
103
104 Ok(())
105 }
106}
107
108impl<C: ChainSpecParser> ImportEraCommand<C> {
109 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
111 Some(&self.env.chain)
112 }
113}