Skip to main content

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::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/// Syncs ERA encoded blocks from a local or remote source.
21#[derive(Debug, Parser)]
22pub struct ImportEraCommand<C: ChainSpecParser> {
23    #[command(flatten)]
24    env: EnvironmentArgs<C>,
25
26    #[clap(flatten)]
27    import: ImportArgs,
28
29    /// Stop the import after this block height has been reached.
30    ///
31    /// The file containing the block is imported up to and including this height, then the
32    /// import ends. By default all available blocks are imported.
33    #[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    /// The path to a directory for import.
41    ///
42    /// The ERA1 files are read from the local directory parsing headers and bodies.
43    #[arg(long, value_name = "IMPORT_ERA_PATH", verbatim_doc_comment)]
44    path: Option<PathBuf>,
45
46    /// The URL to a remote host where the ERA1 files are hosted.
47    ///
48    /// The ERA1 files are read from the remote host using HTTP GET requests parsing headers
49    /// and bodies.
50    #[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    /// Execute `import-era` command
75    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    /// Returns the underlying chain being used to run this command
155    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
156        Some(&self.env.chain)
157    }
158}