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 #[arg(long, verbatim_doc_comment)]
43 with_receipts: bool,
44}
45
46#[derive(Debug, Args)]
47#[group(required = false, multiple = false)]
48pub struct ImportArgs {
49 #[arg(long, value_name = "IMPORT_ERA_PATH", verbatim_doc_comment)]
53 path: Option<PathBuf>,
54
55 #[arg(long, value_name = "IMPORT_ERA_URL", verbatim_doc_comment)]
60 url: Option<Url>,
61}
62
63trait TryFromChain {
64 fn try_to_url(&self) -> eyre::Result<Url>;
65}
66
67impl TryFromChain for ChainKind {
68 fn try_to_url(&self) -> eyre::Result<Url> {
69 Ok(match self {
70 ChainKind::Named(NamedChain::Mainnet) => {
71 Url::parse("https://era.ithaca.xyz/era1/index.html").expect("URL should be valid")
72 }
73 ChainKind::Named(NamedChain::Sepolia) => {
74 Url::parse("https://era.ithaca.xyz/sepolia-era1/index.html")
75 .expect("URL should be valid")
76 }
77 chain => return Err(eyre!("No known host for ERA files on chain {chain:?}")),
78 })
79 }
80}
81
82impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportEraCommand<C> {
83 pub async fn execute<N>(self, runtime: reth_tasks::Runtime) -> eyre::Result<()>
85 where
86 N: CliNodeTypes<ChainSpec = C::ChainSpec>,
87 {
88 info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
89
90 let access =
93 if self.with_receipts { AccessRights::RwInconsistent } else { AccessRights::RW };
94 let Environment { provider_factory, config, .. } = self.env.init::<N>(access, runtime)?;
95
96 let mut hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir);
97
98 let static_file_provider = provider_factory.static_file_provider();
99 let genesis_block_number = static_file_provider.genesis_block_number();
101 let headers_tip = static_file_provider
102 .get_highest_static_file_block(StaticFileSegment::Headers)
103 .unwrap_or(genesis_block_number);
104
105 let resume_block = if self.with_receipts {
108 let receipts_tip = static_file_provider
109 .get_highest_static_file_block(StaticFileSegment::Receipts)
110 .unwrap_or(genesis_block_number);
111 headers_tip.min(receipts_tip)
112 } else {
113 headers_tip
114 };
115 let next_block = resume_block + 1;
116
117 let chain_spec = self.env.chain.clone();
120 let is_receipt_verifiable =
121 move |number: u64| chain_spec.is_byzantium_active_at_block(number);
122
123 if let Some(path) = self.import.path {
124 let era_type = EraFileType::from_dir(&path)?.ok_or_else(|| {
125 eyre!(
126 "No ERA (.era), ERA1 (.era1) or ERE (.ere, .erae) files found in {}",
127 path.display()
128 )
129 })?;
130
131 info!(target: "reth::cli", ?era_type, path = %path.display(), to_block = ?self.to_block, with_receipts = self.with_receipts, "Starting ERA import");
132
133 check_receipts_supported(self.with_receipts, era_type)?;
134
135 match era_type {
136 EraFileType::Era => era::import::<era::Era, _, _, _, _, _, _>(
137 read_era_dir(path)?,
138 &provider_factory,
139 &mut hash_collector,
140 self.to_block,
141 self.with_receipts,
142 &is_receipt_verifiable,
143 )?,
144 EraFileType::Ere => era::import::<era::Ere, _, _, _, _, _, _>(
145 read_dir(path, next_block)?,
146 &provider_factory,
147 &mut hash_collector,
148 self.to_block,
149 self.with_receipts,
150 &is_receipt_verifiable,
151 )?,
152 EraFileType::Era1 => era::import::<era::Era1, _, _, _, _, _, _>(
153 read_dir(path, next_block)?,
154 &provider_factory,
155 &mut hash_collector,
156 self.to_block,
157 self.with_receipts,
158 &is_receipt_verifiable,
159 )?,
160 };
161 } else {
162 let url = match self.import.url {
163 Some(url) => url,
164 None => self.env.chain.chain().kind().try_to_url()?,
165 };
166 let era_type = EraFileType::from_url(url.as_str());
167
168 info!(target: "reth::cli", ?era_type, %url, to_block = ?self.to_block, with_receipts = self.with_receipts, "Starting ERA import");
169
170 check_receipts_supported(self.with_receipts, era_type)?;
171
172 let folder =
173 self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era");
174
175 fs::create_dir_all(&folder)?;
176
177 let mut config = EraStreamConfig::default();
178 if !matches!(era_type, EraFileType::Era) {
182 config = config.start_from(next_block);
183 }
184 let client = EraClient::new(Client::new(), url, folder).with_era_type(era_type);
185 let stream = EraStream::new(client, config);
186
187 match era_type {
188 EraFileType::Ere => era::import::<era::Ere, _, _, _, _, _, _>(
189 stream,
190 &provider_factory,
191 &mut hash_collector,
192 self.to_block,
193 self.with_receipts,
194 &is_receipt_verifiable,
195 )?,
196 EraFileType::Era1 => era::import::<era::Era1, _, _, _, _, _, _>(
197 stream,
198 &provider_factory,
199 &mut hash_collector,
200 self.to_block,
201 self.with_receipts,
202 &is_receipt_verifiable,
203 )?,
204 EraFileType::Era => era::import::<era::Era, _, _, _, _, _, _>(
205 stream,
206 &provider_factory,
207 &mut hash_collector,
208 self.to_block,
209 self.with_receipts,
210 &is_receipt_verifiable,
211 )?,
212 };
213 }
214
215 Ok(())
216 }
217}
218
219fn check_receipts_supported(with_receipts: bool, era_type: EraFileType) -> eyre::Result<()> {
222 if with_receipts && era_type == EraFileType::Era {
223 return Err(eyre!(
224 "--with-receipts is not supported for `.era` files: they contain no receipt data. \
225 Use `.era1` or `.ere` files instead."
226 ));
227 }
228 Ok(())
229}
230
231impl<C: ChainSpecParser> ImportEraCommand<C> {
232 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
234 Some(&self.env.chain)
235 }
236}