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