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, OptionExt};
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_node_core::{dirs::data_dir, version::SHORT_VERSION};
13use std::{path::PathBuf, sync::Arc};
14use tracing::info;
15
16/// Syncs ERA encoded blocks from a local or remote source.
17#[derive(Debug, Parser)]
18pub struct ImportEraCommand<C: ChainSpecParser> {
19    #[command(flatten)]
20    env: EnvironmentArgs<C>,
21
22    #[clap(flatten)]
23    import: ImportArgs,
24}
25
26#[derive(Debug, Args)]
27#[group(required = false, multiple = false)]
28pub struct ImportArgs {
29    /// The path to a directory for import.
30    ///
31    /// The ERA1 files are read from the local directory parsing headers and bodies.
32    #[arg(long, value_name = "IMPORT_ERA_PATH", verbatim_doc_comment)]
33    path: Option<PathBuf>,
34
35    /// The URL to a remote host where the ERA1 files are hosted.
36    ///
37    /// The ERA1 files are read from the remote host using HTTP GET requests parsing headers
38    /// and bodies.
39    #[arg(long, value_name = "IMPORT_ERA_URL", verbatim_doc_comment)]
40    url: Option<Url>,
41}
42
43trait TryFromChain {
44    fn try_to_url(&self) -> eyre::Result<Url>;
45}
46
47impl TryFromChain for ChainKind {
48    fn try_to_url(&self) -> eyre::Result<Url> {
49        Ok(match self {
50            ChainKind::Named(NamedChain::Mainnet) => {
51                Url::parse("https://era.ithaca.xyz/era1/").expect("URL should be valid")
52            }
53            ChainKind::Named(NamedChain::Sepolia) => {
54                Url::parse("https://era.ithaca.xyz/sepolia-era1/").expect("URL should be valid")
55            }
56            chain => return Err(eyre!("No known host for ERA files on chain {chain:?}")),
57        })
58    }
59}
60
61impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportEraCommand<C> {
62    /// Execute `import-era` command
63    pub async fn execute<N>(self) -> eyre::Result<()>
64    where
65        N: CliNodeTypes<ChainSpec = C::ChainSpec>,
66    {
67        info!(target: "reth::cli", "reth {} starting", SHORT_VERSION);
68
69        let Environment { provider_factory, config, .. } = self.env.init::<N>(AccessRights::RW)?;
70
71        let hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir);
72        let provider_factory = &provider_factory.provider_rw()?.0;
73
74        if let Some(path) = self.import.path {
75            let stream = read_dir(path)?;
76
77            era::import(stream, provider_factory, hash_collector)?;
78        } else {
79            let url = match self.import.url {
80                Some(url) => url,
81                None => self.env.chain.chain().kind().try_to_url()?,
82            };
83            let folder = data_dir().ok_or_eyre("Missing data directory")?.join("era");
84            let folder = folder.into_boxed_path();
85            let client = EraClient::new(Client::new(), url, folder);
86            let stream = EraStream::new(client, EraStreamConfig::default());
87
88            era::import(stream, provider_factory, hash_collector)?;
89        }
90
91        Ok(())
92    }
93}
94
95impl<C: ChainSpecParser> ImportEraCommand<C> {
96    /// Returns the underlying chain being used to run this command
97    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
98        Some(&self.env.chain)
99    }
100}