reth_cli_commands/
import_era.rs

1//! Command that initializes the node by importing a chain from ERA files.
2use 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/// Syncs ERA encoded blocks from a local or remote source.
20#[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    /// The path to a directory for import.
33    ///
34    /// The ERA1 files are read from the local directory parsing headers and bodies.
35    #[arg(long, value_name = "IMPORT_ERA_PATH", verbatim_doc_comment)]
36    path: Option<PathBuf>,
37
38    /// The URL to a remote host where the ERA1 files are hosted.
39    ///
40    /// The ERA1 files are read from the remote host using HTTP GET requests parsing headers
41    /// and bodies.
42    #[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    /// Execute `import-era` command
67    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    /// Returns the underlying chain being used to run this command
110    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
111        Some(&self.env.chain)
112    }
113}