diff --git a/CHANGELOG.md b/CHANGELOG.md index c56e56116f62..194ce955b1d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ ### Added +- [#6061](https://github.com/ChainSafe/forest/pull/6061) Added `forest-cli state actor-cids` command for listing all actor CIDs in the state tree for the current tipset. + ### Changed ### Removed diff --git a/scripts/tests/calibnet_other_check.sh b/scripts/tests/calibnet_other_check.sh index 62372fcf667f..872f1be03860 100755 --- a/scripts/tests/calibnet_other_check.sh +++ b/scripts/tests/calibnet_other_check.sh @@ -59,6 +59,14 @@ $FOREST_CLI_PATH healthcheck healthy --wait echo "Test subcommand: healthcheck ready" $FOREST_CLI_PATH healthcheck ready --wait +echo "Test subcommand: state actor-cids" +bundle_cid=$($FOREST_CLI_PATH state actor-cids --format json | jq -r '.Bundle["/"]') +manifest_path="./build/manifest.json" +if ! grep -q "$bundle_cid" "$manifest_path"; then + echo "Bundle CID $bundle_cid not found in $manifest_path" + exit 1 +fi + echo "Regression testing mempool select" gem install http --user-install $FOREST_CLI_PATH chain head --format json -n 1000 | scripts/mpool_select_killer.rb diff --git a/src/cli/subcommands/state_cmd.rs b/src/cli/subcommands/state_cmd.rs index 3318469ea628..273cffc4e489 100644 --- a/src/cli/subcommands/state_cmd.rs +++ b/src/cli/subcommands/state_cmd.rs @@ -12,6 +12,12 @@ use std::num::NonZeroUsize; use std::path::PathBuf; use std::time::Duration; +#[derive(Debug, Clone, clap::ValueEnum)] +pub enum Format { + Json, + Text, +} + #[derive(Debug, Subcommand)] pub enum StateCommands { Fetch { @@ -37,6 +43,12 @@ pub enum StateCommands { /// Actor address to read the state of actor_address: StrictAddress, }, + /// Returns the built-in actor bundle CIDs for the current network + ActorCids { + /// Format output + #[arg(long, default_value = "text")] + format: Format, + }, } impl StateCommands { @@ -83,6 +95,16 @@ impl StateCommands { .await?; println!("{}", ret.state.into_lotus_json_string_pretty()?); } + Self::ActorCids { format } => { + let info = client.call(StateActorInfo::request(())?).await?; + + match format { + Format::Json => { + println!("{}", serde_json::to_string_pretty(&info)?); + } + Format::Text => println!("{info}"), + } + } } Ok(()) } diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index 7be0d5596be1..9bca3a5da154 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -11,16 +11,16 @@ use crate::cid_collections::CidHashSet; use crate::eth::EthChainId; use crate::interpreter::{MessageCallbackCtx, VMTrace}; use crate::libp2p::NetworkMessage; -use crate::lotus_json::lotus_json_with_self; +use crate::lotus_json::{LotusJson, lotus_json_with_self}; use crate::networks::ChainConfig; use crate::rpc::registry::actors_reg::load_and_serialize_actor_state; -use crate::shim::actors::init; use crate::shim::actors::market::DealState; use crate::shim::actors::market::ext::MarketStateExt as _; use crate::shim::actors::miner::ext::DeadlineExt; use crate::shim::actors::state_load::*; use crate::shim::actors::verifreg::ext::VerifiedRegistryStateExt as _; use crate::shim::actors::verifreg::{Allocation, AllocationID, Claim}; +use crate::shim::actors::{init, system}; use crate::shim::actors::{ market, miner, miner::{MinerInfo, MinerPower}, @@ -32,6 +32,7 @@ use crate::shim::actors::{ power::ext::PowerStateExt as _, }; use crate::shim::address::Payload; +use crate::shim::machine::BuiltinActorManifest; use crate::shim::message::{Message, MethodNum}; use crate::shim::piece::PaddedPieceSize; use crate::shim::sector::{SectorNumber, SectorSize}; @@ -49,7 +50,7 @@ use crate::{ rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError, types::*}, }; use ahash::{HashMap, HashMapExt, HashSet}; -use anyhow::Context as _; +use anyhow::Context; use anyhow::Result; use cid::Cid; use enumflags2::{BitFlags, make_bitflags}; @@ -3164,3 +3165,96 @@ fn get_pledge_ramp_params( Ok((0, 0)) } } + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "PascalCase")] +pub struct StateActorCodeCidsOutput { + pub network_version: NetworkVersion, + pub network_version_revision: i64, + pub actor_version: String, + #[serde(with = "crate::lotus_json")] + #[schemars(with = "LotusJson")] + pub manifest: Cid, + #[serde(with = "crate::lotus_json")] + #[schemars(with = "LotusJson")] + pub bundle: Cid, + #[serde(with = "crate::lotus_json")] + #[schemars(with = "LotusJson>")] + pub actor_cids: HashMap, +} +lotus_json_with_self!(StateActorCodeCidsOutput); + +impl std::fmt::Display for StateActorCodeCidsOutput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Network Version: {}", self.network_version)?; + writeln!( + f, + "Network Version Revision: {}", + self.network_version_revision + )?; + writeln!(f, "Actor Version: {}", self.actor_version)?; + writeln!(f, "Manifest CID: {}", self.manifest)?; + writeln!(f, "Bundle CID: {}", self.bundle)?; + writeln!(f, "Actor CIDs:")?; + let longest_name = self + .actor_cids + .keys() + .map(|name| name.len()) + .max() + .unwrap_or(0); + for (name, cid) in &self.actor_cids { + writeln!(f, " {:width$} : {}", name, cid, width = longest_name)?; + } + Ok(()) + } +} + +pub enum StateActorInfo {} + +impl RpcMethod<0> for StateActorInfo { + const NAME: &'static str = "Forest.StateActorInfo"; + const PARAM_NAMES: [&'static str; 0] = []; + const API_PATHS: BitFlags = ApiPaths::all(); + const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = + Some("Returns the builtin actor information for the current network."); + + type Params = (); + type Ok = StateActorCodeCidsOutput; + + async fn handle( + ctx: Ctx, + (): Self::Params, + ) -> Result { + let ts = ctx.chain_store().load_required_tipset_or_heaviest(None)?; + let state_tree = StateTree::new_from_tipset(ctx.store_owned(), &ts)?; + let bundle = state_tree.get_actor_bundle_metadata()?; + let system_state: system::State = state_tree.get_actor_state()?; + let actors = system_state.builtin_actors_cid(); + + let current_manifest = BuiltinActorManifest::load_v1_actor_list(ctx.store(), actors)?; + + // Sanity check: the command would normally be used only for diagnostics, so we want to be + // sure the data is consistent. + if current_manifest != bundle.manifest { + return Err(anyhow::anyhow!("Actor bundle manifest does not match the manifest in the state tree. This indicates that the node is misconfigured or is running an unsupported network.") + .into()); + } + + let network_version = ctx.chain_config().network_version(ts.epoch() - 1); + let network_version_revision = ctx.chain_config().network_version_revision(ts.epoch() - 1); + let result = StateActorCodeCidsOutput { + network_version, + network_version_revision, + actor_version: bundle.version.to_owned(), + manifest: current_manifest.actor_list_cid, + bundle: bundle.bundle_cid, + actor_cids: current_manifest + .builtin_actors() + .map(|(a, c)| (a.name().to_string(), c)) + .collect(), + }; + + Ok(result) + } +} diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index b175b0427cd7..55461f7fb3dd 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -231,6 +231,7 @@ macro_rules! for_each_rpc_method { $callback!($crate::rpc::state::StateMinerSectors); $callback!($crate::rpc::state::StateNetworkName); $callback!($crate::rpc::state::StateNetworkVersion); + $callback!($crate::rpc::state::StateActorInfo); $callback!($crate::rpc::state::StateReadState); $callback!($crate::rpc::state::StateDecodeParams); $callback!($crate::rpc::state::StateReplay); diff --git a/src/shim/actors/builtin/system/mod.rs b/src/shim/actors/builtin/system/mod.rs index 5bf52de94a6c..583740227f32 100644 --- a/src/shim/actors/builtin/system/mod.rs +++ b/src/shim/actors/builtin/system/mod.rs @@ -1,6 +1,7 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use cid::Cid; use fvm_shared2::address::Address; use serde::Serialize; @@ -25,3 +26,21 @@ pub enum State { V16(fil_actor_system_state::v16::State), V17(fil_actor_system_state::v17::State), } + +impl State { + /// Returns the builtin actors Cid. + pub fn builtin_actors_cid(&self) -> &Cid { + match self { + State::V8(s) => &s.builtin_actors, + State::V9(s) => &s.builtin_actors, + State::V10(s) => &s.builtin_actors, + State::V11(s) => &s.builtin_actors, + State::V12(s) => &s.builtin_actors, + State::V13(s) => &s.builtin_actors, + State::V14(s) => &s.builtin_actors, + State::V15(s) => &s.builtin_actors, + State::V16(s) => &s.builtin_actors, + State::V17(s) => &s.builtin_actors, + } + } +} diff --git a/src/shim/machine/manifest.rs b/src/shim/machine/manifest.rs index 1be8c9649557..2c726b25af3f 100644 --- a/src/shim/machine/manifest.rs +++ b/src/shim/machine/manifest.rs @@ -24,7 +24,7 @@ pub use fil_actors_shared::v11::runtime::builtins::Type as BuiltinActor; pub struct BuiltinActorManifest { builtin2cid: BTreeMap, /// The CID that this manifest was built from - actor_list_cid: Cid, + pub actor_list_cid: Cid, } // We need to implement Serialize and Deserialize manually because `BuiltinActor` is not `Serialize` or `Deserialize`, diff --git a/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt b/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt index 5b26058d7984..e174ae3c0d9a 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots_ignored.txt @@ -81,6 +81,7 @@ Forest.ChainExport Forest.ChainGetMinBaseFee Forest.NetInfo Forest.SnapshotGC +Forest.StateActorInfo Forest.StateCompute Forest.StateFetchRoot Forest.SyncSnapshotProgress