1use 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::common::file_ops::EraFileType;
10use reth_era_downloader::{read_dir, EraClient, EraStream, EraStreamConfig};
11use reth_era_utils as era;
12use reth_etl::Collector;
13use reth_fs_util as fs;
14use reth_node_core::version::version_metadata;
15use reth_provider::StaticFileProviderFactory;
16use reth_static_file_types::StaticFileSegment;
17use std::{path::PathBuf, sync::Arc};
18use tracing::info;
19
20#[derive(Debug, Parser)]
22pub struct ImportEraCommand<C: ChainSpecParser> {
23 #[command(flatten)]
24 env: EnvironmentArgs<C>,
25
26 #[clap(flatten)]
27 import: ImportArgs,
28
29 #[arg(long, value_name = "TO_BLOCK", verbatim_doc_comment)]
34 to_block: Option<u64>,
35}
36
37#[derive(Debug, Args)]
38#[group(required = false, multiple = false)]
39pub struct ImportArgs {
40 #[arg(long, value_name = "IMPORT_ERA_PATH", verbatim_doc_comment)]
44 path: Option<PathBuf>,
45
46 #[arg(long, value_name = "IMPORT_ERA_URL", verbatim_doc_comment)]
51 url: Option<Url>,
52}
53
54trait TryFromChain {
55 fn try_to_url(&self) -> eyre::Result<Url>;
56}
57
58impl TryFromChain for ChainKind {
59 fn try_to_url(&self) -> eyre::Result<Url> {
60 Ok(match self {
61 ChainKind::Named(NamedChain::Mainnet) => {
62 Url::parse("https://era.ithaca.xyz/era1/index.html").expect("URL should be valid")
63 }
64 ChainKind::Named(NamedChain::Sepolia) => {
65 Url::parse("https://era.ithaca.xyz/sepolia-era1/index.html")
66 .expect("URL should be valid")
67 }
68 chain => return Err(eyre!("No known host for ERA files on chain {chain:?}")),
69 })
70 }
71}
72
73impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportEraCommand<C> {
74 pub async fn execute<N>(self, runtime: reth_tasks::Runtime) -> eyre::Result<()>
76 where
77 N: CliNodeTypes<ChainSpec = C::ChainSpec>,
78 {
79 info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
80
81 let Environment { provider_factory, config, .. } =
82 self.env.init::<N>(AccessRights::RW, runtime)?;
83
84 let mut hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir);
85
86 let next_block = provider_factory
87 .static_file_provider()
88 .get_highest_static_file_block(StaticFileSegment::Headers)
89 .unwrap_or_default() +
90 1;
91
92 if let Some(path) = self.import.path {
93 let era_type = EraFileType::from_dir(&path)?.ok_or_else(|| {
94 eyre!("No ERA1 (.era1) or ERE (.ere, .erae) files found in {}", path.display())
95 })?;
96
97 info!(target: "reth::cli", ?era_type, path = %path.display(), to_block = ?self.to_block, "Starting ERA import");
98
99 let stream = read_dir(path, next_block)?;
100
101 match era_type {
102 EraFileType::Ere => era::import::<era::Ere, _, _, _, _, _, _>(
103 stream,
104 &provider_factory,
105 &mut hash_collector,
106 self.to_block,
107 )?,
108 _ => era::import::<era::Era1, _, _, _, _, _, _>(
109 stream,
110 &provider_factory,
111 &mut hash_collector,
112 self.to_block,
113 )?,
114 };
115 } else {
116 let url = match self.import.url {
117 Some(url) => url,
118 None => self.env.chain.chain().kind().try_to_url()?,
119 };
120 let era_type = EraFileType::from_url(url.as_str());
121
122 info!(target: "reth::cli", ?era_type, %url, to_block = ?self.to_block, "Starting ERA import");
123
124 let folder =
125 self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era");
126
127 fs::create_dir_all(&folder)?;
128
129 let config = EraStreamConfig::default().start_from(next_block);
130 let client = EraClient::new(Client::new(), url, folder);
131 let stream = EraStream::new(client, config);
132
133 match era_type {
134 EraFileType::Ere => era::import::<era::Ere, _, _, _, _, _, _>(
135 stream,
136 &provider_factory,
137 &mut hash_collector,
138 self.to_block,
139 )?,
140 _ => era::import::<era::Era1, _, _, _, _, _, _>(
141 stream,
142 &provider_factory,
143 &mut hash_collector,
144 self.to_block,
145 )?,
146 };
147 }
148
149 Ok(())
150 }
151}
152
153impl<C: ChainSpecParser> ImportEraCommand<C> {
154 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
156 Some(&self.env.chain)
157 }
158}