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, read_era_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!(
95 "No ERA (.era), ERA1 (.era1) or ERE (.ere, .erae) files found in {}",
96 path.display()
97 )
98 })?;
99
100 info!(target: "reth::cli", ?era_type, path = %path.display(), to_block = ?self.to_block, "Starting ERA import");
101
102 match era_type {
103 EraFileType::Era => era::import::<era::Era, _, _, _, _, _, _>(
104 read_era_dir(path)?,
105 &provider_factory,
106 &mut hash_collector,
107 self.to_block,
108 )?,
109 EraFileType::Ere => era::import::<era::Ere, _, _, _, _, _, _>(
110 read_dir(path, next_block)?,
111 &provider_factory,
112 &mut hash_collector,
113 self.to_block,
114 )?,
115 EraFileType::Era1 => era::import::<era::Era1, _, _, _, _, _, _>(
116 read_dir(path, next_block)?,
117 &provider_factory,
118 &mut hash_collector,
119 self.to_block,
120 )?,
121 };
122 } else {
123 let url = match self.import.url {
124 Some(url) => url,
125 None => self.env.chain.chain().kind().try_to_url()?,
126 };
127 let era_type = EraFileType::from_url(url.as_str());
128
129 info!(target: "reth::cli", ?era_type, %url, to_block = ?self.to_block, "Starting ERA import");
130
131 let folder =
132 self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era");
133
134 fs::create_dir_all(&folder)?;
135
136 let mut config = EraStreamConfig::default();
137 if !matches!(era_type, EraFileType::Era) {
141 config = config.start_from(next_block);
142 }
143 let client = EraClient::new(Client::new(), url, folder).with_era_type(era_type);
144 let stream = EraStream::new(client, config);
145
146 match era_type {
147 EraFileType::Ere => era::import::<era::Ere, _, _, _, _, _, _>(
148 stream,
149 &provider_factory,
150 &mut hash_collector,
151 self.to_block,
152 )?,
153 EraFileType::Era1 => era::import::<era::Era1, _, _, _, _, _, _>(
154 stream,
155 &provider_factory,
156 &mut hash_collector,
157 self.to_block,
158 )?,
159 EraFileType::Era => era::import::<era::Era, _, _, _, _, _, _>(
160 stream,
161 &provider_factory,
162 &mut hash_collector,
163 self.to_block,
164 )?,
165 };
166 }
167
168 Ok(())
169 }
170}
171
172impl<C: ChainSpecParser> ImportEraCommand<C> {
173 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
175 Some(&self.env.chain)
176 }
177}