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, 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/// 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!(
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            // `start_from` maps a block number to a file index as `block / BLOCKS_PER_FILE`, valid
138            // only for execution-layer files (era1/ere). Consensus `.era` files are slot-indexed,
139            // so stream from 0 and let the pipeline skip already-imported blocks.
140            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    /// Returns the underlying chain being used to run this command
174    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
175        Some(&self.env.chain)
176    }
177}