From e8634421f6ccafafa2224d98f0b3515687ca6ccb Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 18 Sep 2025 22:26:44 +0800 Subject: [PATCH 1/2] feat: allow export spine-only snapshots with `forest-cli snapshot export -d 0` --- src/chain_sync/chain_muxer.rs | 2 +- src/chain_sync/mod.rs | 2 +- src/cli/subcommands/snapshot_cmd.rs | 21 +++++++++++++-------- src/rpc/methods/chain.rs | 8 -------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/chain_sync/chain_muxer.rs b/src/chain_sync/chain_muxer.rs index a8db577977b2..8b3ddda51695 100644 --- a/src/chain_sync/chain_muxer.rs +++ b/src/chain_sync/chain_muxer.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; -const DEFAULT_RECENT_STATE_ROOTS: i64 = 2000; +pub const DEFAULT_RECENT_STATE_ROOTS: i64 = 2000; /// Structure that defines syncing configuration options #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] diff --git a/src/chain_sync/mod.rs b/src/chain_sync/mod.rs index b1631b12fe53..bd27bab6195b 100644 --- a/src/chain_sync/mod.rs +++ b/src/chain_sync/mod.rs @@ -3,7 +3,7 @@ mod bad_block_cache; mod chain_follower; -mod chain_muxer; +pub mod chain_muxer; pub mod consensus; pub mod metrics; pub mod network_context; diff --git a/src/cli/subcommands/snapshot_cmd.rs b/src/cli/subcommands/snapshot_cmd.rs index c157c38c578e..8bf01d52ecc0 100644 --- a/src/cli/subcommands/snapshot_cmd.rs +++ b/src/cli/subcommands/snapshot_cmd.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use crate::chain::FilecoinSnapshotVersion; -use crate::chain_sync::SyncConfig; +use crate::chain_sync::chain_muxer::DEFAULT_RECENT_STATE_ROOTS; use crate::cli_shared::snapshot::{self, TrustedVendor}; use crate::db::car::forest::new_forest_car_temp_path_in; use crate::networks::calibnet; @@ -33,10 +33,10 @@ pub enum SnapshotCommands { /// Tipset to start the export from, default is the chain head #[arg(short, long)] tipset: Option, - /// How many state-roots to include. Lower limit is 900 for `calibnet` and `mainnet`. - #[arg(short, long)] - depth: Option, - /// Export snapshot in the experimental v2 format(FRC-0108). + /// How many state trees to include. 0 for chain spine with no state trees. + #[arg(short, long, default_value_t = DEFAULT_RECENT_STATE_ROOTS)] + depth: crate::chain::ChainEpochDelta, + /// Snapshot format to export. #[arg(long, value_enum, default_value_t = FilecoinSnapshotVersion::V1)] format: FilecoinSnapshotVersion, }, @@ -67,8 +67,13 @@ impl SnapshotCommands { raw_network_name.as_str() }; - let tipset = - ChainGetTipSetByHeight::call(&client, (epoch, Default::default())).await?; + // This could take long when the requested epoch is far behind the chain head + let tipset = client + .call( + ChainGetTipSetByHeight::request((epoch, Default::default()))? + .with_timeout(Duration::from_secs(60 * 15)), + ) + .await?; let output_path = match output_path.is_dir() { true => output_path.join(snapshot::filename( @@ -90,7 +95,7 @@ impl SnapshotCommands { let params = ForestChainExportParams { version: format, epoch, - recent_roots: depth.unwrap_or(SyncConfig::default().recent_state_roots), + recent_roots: depth, output_path: temp_path.to_path_buf(), tipset_keys: ApiTipsetKey(Some(chain_head.key().clone())), skip_checksum, diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 53a502dc4ced..0a8681121e4e 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -332,14 +332,6 @@ impl RpcMethod<1> for ForestChainExport { return Err(anyhow::anyhow!("Another chain export job is still in progress").into()); } - let chain_finality = ctx.chain_config().policy.chain_finality; - if recent_roots < chain_finality { - return Err(anyhow::anyhow!(format!( - "recent-stateroots must be greater than {chain_finality}" - )) - .into()); - } - let head = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; let start_ts = ctx.chain_index() From efa420488bc48b5f8f4972257496d43e7d8abd33 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 19 Sep 2025 14:21:25 +0800 Subject: [PATCH 2/2] resolve comments --- src/cli/subcommands/snapshot_cmd.rs | 39 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/cli/subcommands/snapshot_cmd.rs b/src/cli/subcommands/snapshot_cmd.rs index 8ecb9183875f..941ad3cf206c 100644 --- a/src/cli/subcommands/snapshot_cmd.rs +++ b/src/cli/subcommands/snapshot_cmd.rs @@ -7,7 +7,8 @@ use crate::cli_shared::snapshot::{self, TrustedVendor}; use crate::db::car::forest::new_forest_car_temp_path_in; use crate::networks::calibnet; use crate::rpc::chain::ForestChainExportDiffParams; -use crate::rpc::{self, chain::ForestChainExportParams, prelude::*, types::ApiTipsetKey}; +use crate::rpc::{self, chain::ForestChainExportParams, prelude::*}; +use crate::shim::policy::policy_constants::CHAIN_FINALITY; use anyhow::Context as _; use chrono::DateTime; use clap::Subcommand; @@ -69,12 +70,18 @@ impl SnapshotCommands { depth, format, } => { - let chain_head = ChainHead::call(&client, ()).await?; + anyhow::ensure!( + depth >= 0, + "--depth must be non-negative; use 0 for spine-only snapshots" + ); - let epoch = tipset.unwrap_or(chain_head.epoch()); + if depth < CHAIN_FINALITY { + tracing::warn!( + "Depth {depth} should be no less than CHAIN_FINALITY {CHAIN_FINALITY} to export a valid lite snapshot" + ); + } let raw_network_name = StateNetworkName::call(&client, ()).await?; - // For historical reasons and backwards compatibility if snapshot services or their // consumers relied on the `calibnet`, we use `calibnet` as the chain name. let chain_name = if raw_network_name == calibnet::NETWORK_GENESIS_NAME { @@ -83,13 +90,17 @@ impl SnapshotCommands { raw_network_name.as_str() }; - // This could take long when the requested epoch is far behind the chain head - let tipset = client - .call( - ChainGetTipSetByHeight::request((epoch, Default::default()))? - .with_timeout(Duration::from_secs(60 * 15)), - ) - .await?; + let tipset = if let Some(epoch) = tipset { + // This could take a while when the requested epoch is far behind the chain head + client + .call( + ChainGetTipSetByHeight::request((epoch, Default::default()))? + .with_timeout(Duration::from_secs(60 * 15)), + ) + .await? + } else { + ChainHead::call(&client, ()).await? + }; let output_path = match output_path.is_dir() { true => output_path.join(snapshot::filename( @@ -99,7 +110,7 @@ impl SnapshotCommands { .unwrap_or_default() .naive_utc() .date(), - epoch, + tipset.epoch(), true, )), false => output_path.clone(), @@ -110,10 +121,10 @@ impl SnapshotCommands { let params = ForestChainExportParams { version: format, - epoch, + epoch: tipset.epoch(), recent_roots: depth, output_path: temp_path.to_path_buf(), - tipset_keys: ApiTipsetKey(Some(chain_head.key().clone())), + tipset_keys: tipset.key().clone().into(), skip_checksum, dry_run, };