forest/dev/subcommands/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::cli_shared::cli::HELP_MESSAGE;
5use crate::rpc::Client;
6use crate::utils::net::{DownloadFileOption, download_file_with_cache};
7use crate::utils::proofs_api::ensure_proof_params_downloaded;
8use crate::utils::version::FOREST_VERSION_STRING;
9use anyhow::Context as _;
10use clap::Parser;
11use directories::ProjectDirs;
12use std::borrow::Cow;
13use std::path::PathBuf;
14use std::time::Duration;
15use tokio::task::JoinSet;
16use url::Url;
17
18/// Command-line options for the `forest-dev` binary
19#[derive(Parser)]
20#[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")
21)]
22#[command(help_template(HELP_MESSAGE))]
23pub struct Cli {
24    #[command(subcommand)]
25    pub cmd: Subcommand,
26}
27
28/// forest-dev sub-commands
29#[derive(clap::Subcommand)]
30pub enum Subcommand {
31    /// Fetch RPC test snapshots to the local cache
32    FetchRpcTests,
33}
34
35impl Subcommand {
36    pub async fn run(self, _client: Client) -> anyhow::Result<()> {
37        match self {
38            Self::FetchRpcTests => fetch_rpc_tests().await,
39        }
40    }
41}
42
43async fn fetch_rpc_tests() -> anyhow::Result<()> {
44    crate::utils::proofs_api::maybe_set_proofs_parameter_cache_dir_env(
45        &crate::Config::default().client.data_dir,
46    );
47    ensure_proof_params_downloaded().await?;
48    let tests = include_str!("../../tool/subcommands/api_cmd/test_snapshots.txt")
49        .lines()
50        .map(|i| {
51            // Remove comment
52            i.split("#").next().unwrap().trim().to_string()
53        })
54        .filter(|l| !l.is_empty() && !l.starts_with('#'));
55    let mut joinset = JoinSet::new();
56    for test in tests {
57        joinset.spawn(fetch_rpc_test_snapshot(test.into()));
58    }
59    for result in joinset.join_all().await {
60        if let Err(e) = result {
61            tracing::warn!("{e}");
62        }
63    }
64    Ok(())
65}
66
67pub async fn fetch_rpc_test_snapshot<'a>(name: Cow<'a, str>) -> anyhow::Result<PathBuf> {
68    let url: Url =
69        format!("https://forest-snapshots.fra1.cdn.digitaloceanspaces.com/rpc_test/{name}")
70            .parse()
71            .with_context(|| format!("Failed to parse URL for test: {name}"))?;
72    let project_dir =
73        ProjectDirs::from("com", "ChainSafe", "Forest").context("failed to get project dir")?;
74    let cache_dir = project_dir.cache_dir().join("test").join("rpc-snapshots");
75    let path = crate::utils::retry(
76        crate::utils::RetryArgs {
77            timeout: Some(Duration::from_secs(30)),
78            max_retries: Some(5),
79            delay: Some(Duration::from_secs(1)),
80        },
81        || download_file_with_cache(&url, &cache_dir, DownloadFileOption::NonResumable),
82    )
83    .await
84    .map_err(|e| anyhow::anyhow!("failed to fetch rpc test snapshot {name} :{e}"))?
85    .path;
86    Ok(path)
87}