Skip to main content

forest/dev/subcommands/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4mod state_cmd;
5mod update_checkpoints_cmd;
6
7use crate::cli_shared::cli::HELP_MESSAGE;
8use crate::networks::generate_actor_bundle;
9use crate::rpc::Client;
10use crate::state_manager::utils::state_compute::{
11    get_state_snapshot_file, list_state_snapshot_files,
12};
13use crate::utils::net::{DownloadFileOption, download_file_with_cache};
14use crate::utils::proofs_api::ensure_proof_params_downloaded;
15use crate::utils::version::FOREST_VERSION_STRING;
16use anyhow::Context as _;
17use clap::Parser;
18use directories::ProjectDirs;
19use std::borrow::Cow;
20use std::path::PathBuf;
21use std::time::Duration;
22use tokio::task::JoinSet;
23use url::Url;
24
25/// Command-line options for the `forest-dev` binary
26#[derive(Parser)]
27#[command(name = env!("CARGO_PKG_NAME"), bin_name = "forest-dev", author = env!("CARGO_PKG_AUTHORS"), version = FOREST_VERSION_STRING.as_str(), about = env!("CARGO_PKG_DESCRIPTION")
28)]
29#[command(help_template(HELP_MESSAGE))]
30pub struct Cli {
31    #[command(subcommand)]
32    pub cmd: Subcommand,
33}
34
35/// forest-dev sub-commands
36#[derive(clap::Subcommand)]
37pub enum Subcommand {
38    /// Fetch test snapshots to the local cache
39    FetchTestSnapshots {
40        // Save actor bundle to
41        #[arg(long)]
42        actor_bundle: Option<PathBuf>,
43    },
44    #[command(subcommand)]
45    State(state_cmd::StateCommand),
46    /// Update known blocks (checkpoints), normally in `build/known_blocks.yaml`, by querying RPC
47    /// endpoints
48    UpdateCheckpoints(update_checkpoints_cmd::UpdateCheckpointsCommand),
49}
50
51impl Subcommand {
52    pub async fn run(self, _client: Client) -> anyhow::Result<()> {
53        match self {
54            Self::FetchTestSnapshots { actor_bundle } => fetch_test_snapshots(actor_bundle).await,
55            Self::State(cmd) => cmd.run().await,
56            Self::UpdateCheckpoints(cmd) => cmd.run().await,
57        }
58    }
59}
60
61async fn fetch_test_snapshots(actor_bundle: Option<PathBuf>) -> anyhow::Result<()> {
62    // Prepare proof parameter files
63    crate::utils::proofs_api::maybe_set_proofs_parameter_cache_dir_env(
64        &crate::Config::default().client.data_dir,
65    );
66    ensure_proof_params_downloaded().await?;
67
68    // Prepare actor bundles
69    if let Some(actor_bundle) = actor_bundle {
70        generate_actor_bundle(&actor_bundle).await?;
71        println!("Wrote the actors bundle to {}", actor_bundle.display());
72    }
73
74    // Prepare state computation and validation snapshots
75    fetch_state_tests().await?;
76
77    // Prepare RPC test snapshots
78    fetch_rpc_tests().await?;
79
80    Ok(())
81}
82
83pub async fn fetch_state_tests() -> anyhow::Result<()> {
84    let files = list_state_snapshot_files().await?;
85    let mut joinset = JoinSet::new();
86    for file in files {
87        joinset.spawn(async move { get_state_snapshot_file(&file).await });
88    }
89    for result in joinset.join_all().await {
90        if let Err(e) = result {
91            tracing::warn!("{e}");
92        }
93    }
94    Ok(())
95}
96
97async fn fetch_rpc_tests() -> anyhow::Result<()> {
98    let tests = include_str!("../../tool/subcommands/api_cmd/test_snapshots.txt")
99        .lines()
100        .map(|i| {
101            // Remove comment
102            i.split("#").next().unwrap().trim().to_string()
103        })
104        .filter(|l| !l.is_empty() && !l.starts_with('#'));
105    let mut joinset = JoinSet::new();
106    for test in tests {
107        joinset.spawn(fetch_rpc_test_snapshot(test.into()));
108    }
109    for result in joinset.join_all().await {
110        if let Err(e) = result {
111            tracing::warn!("{e}");
112        }
113    }
114    Ok(())
115}
116
117pub async fn fetch_rpc_test_snapshot<'a>(name: Cow<'a, str>) -> anyhow::Result<PathBuf> {
118    let url: Url =
119        format!("https://forest-snapshots.fra1.cdn.digitaloceanspaces.com/rpc_test/{name}")
120            .parse()
121            .with_context(|| format!("Failed to parse URL for test: {name}"))?;
122    let project_dir =
123        ProjectDirs::from("com", "ChainSafe", "Forest").context("failed to get project dir")?;
124    let cache_dir = project_dir.cache_dir().join("test").join("rpc-snapshots");
125    let path = crate::utils::retry(
126        crate::utils::RetryArgs {
127            timeout: Some(Duration::from_secs(30)),
128            max_retries: Some(5),
129            delay: Some(Duration::from_secs(1)),
130        },
131        || download_file_with_cache(&url, &cache_dir, DownloadFileOption::NonResumable),
132    )
133    .await
134    .map_err(|e| anyhow::anyhow!("failed to fetch rpc test snapshot {name} :{e}"))?
135    .path;
136    Ok(path)
137}