forest/tool/subcommands/
index_cmd.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::{path::PathBuf, sync::Arc};
5
6use anyhow::bail;
7use clap::Subcommand;
8
9use crate::chain::ChainStore;
10use crate::chain::index::ResolveNullTipset;
11use crate::cli_shared::{chain_path, read_config};
12use crate::daemon::db_util::load_all_forest_cars;
13use crate::daemon::db_util::{RangeSpec, backfill_db};
14use crate::db::CAR_DB_DIR_NAME;
15use crate::db::car::ManyCar;
16use crate::db::db_engine::{db_root, open_db};
17use crate::genesis::read_genesis_header;
18use crate::networks::NetworkChain;
19use crate::shim::clock::ChainEpoch;
20use crate::state_manager::StateManager;
21use crate::tool::offline_server::server::handle_chain_config;
22
23#[derive(Debug, Subcommand)]
24pub enum IndexCommands {
25    /// Backfill index with Ethereum mappings, events, etc.
26    Backfill {
27        /// Optional TOML file containing forest daemon configuration
28        #[arg(short, long)]
29        config: Option<PathBuf>,
30        /// Optional chain, will override the chain section of configuration file if used
31        #[arg(long)]
32        chain: Option<NetworkChain>,
33        /// The starting tipset epoch for back-filling (inclusive), defaults to chain head
34        #[arg(long)]
35        from: Option<ChainEpoch>,
36        /// Ending tipset epoch for back-filling (inclusive)
37        #[arg(long)]
38        to: Option<ChainEpoch>,
39        /// Number of tipsets for back-filling
40        #[arg(long, conflicts_with = "to")]
41        n_tipsets: Option<usize>,
42    },
43}
44
45impl IndexCommands {
46    pub async fn run(&self) -> anyhow::Result<()> {
47        match self {
48            Self::Backfill {
49                config,
50                chain,
51                from,
52                to,
53                n_tipsets,
54            } => {
55                let spec = match (to, n_tipsets) {
56                    (Some(x), None) => RangeSpec::To(*x),
57                    (None, Some(x)) => RangeSpec::NumTipsets(*x),
58                    (None, None) => {
59                        bail!("You must provide either '--to' or '--n-tipsets'.");
60                    }
61                    _ => unreachable!(), // Clap ensures this case is handled
62                };
63
64                let (_, config) = read_config(config.as_ref(), chain.clone())?;
65
66                let chain_data_path = chain_path(&config);
67                let db_root_dir = db_root(&chain_data_path)?;
68
69                let db_writer = Arc::new(open_db(db_root_dir.clone(), config.db_config())?);
70                let db = Arc::new(ManyCar::new(db_writer.clone()));
71                let forest_car_db_dir = db_root_dir.join(CAR_DB_DIR_NAME);
72
73                load_all_forest_cars(&db, &forest_car_db_dir)?;
74
75                let chain_config = Arc::new(handle_chain_config(&config.chain)?);
76                let genesis_header = read_genesis_header(
77                    None,
78                    chain_config.genesis_bytes(&db).await?.as_deref(),
79                    &db,
80                )
81                .await?;
82
83                let chain_store = Arc::new(ChainStore::new(
84                    db.clone(),
85                    db.clone(),
86                    db.clone(),
87                    db.writer().clone(),
88                    chain_config,
89                    genesis_header.clone(),
90                )?);
91
92                let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
93
94                let head_ts = chain_store.heaviest_tipset();
95
96                println!("Database path: {}", db_root_dir.display());
97                println!("From epoch:    {}", from.unwrap_or_else(|| head_ts.epoch()));
98                println!("{spec}");
99                println!("Head epoch:    {}", head_ts.epoch());
100
101                let from_ts = if let Some(from) = from {
102                    chain_store.chain_index().tipset_by_height(
103                        *from,
104                        head_ts,
105                        ResolveNullTipset::TakeOlder,
106                    )?
107                } else {
108                    head_ts
109                };
110
111                backfill_db(&state_manager, &from_ts, spec).await?;
112
113                Ok(())
114            }
115        }
116    }
117}