diff --git a/Cargo.lock b/Cargo.lock index d25b020d54f1..89946bd29326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9521,6 +9521,7 @@ dependencies = [ "ic-nervous-system-clients", "ic-nns-constants", "ic-sns-governance", + "ic-sns-root", "ic-sns-wasm", "pocket-ic", "serde", diff --git a/rs/nervous_system/agent/BUILD.bazel b/rs/nervous_system/agent/BUILD.bazel index 778fd6c0a06a..88fc914ac533 100644 --- a/rs/nervous_system/agent/BUILD.bazel +++ b/rs/nervous_system/agent/BUILD.bazel @@ -9,6 +9,7 @@ DEPENDENCIES = [ "//rs/nns/constants", "//rs/nns/sns-wasm", "//rs/sns/governance", + "//rs/sns/root", "//rs/types/base_types", "@crate_index//:anyhow", "@crate_index//:candid", diff --git a/rs/nervous_system/agent/Cargo.toml b/rs/nervous_system/agent/Cargo.toml index 4d24a388e03d..6fd6bf16418c 100644 --- a/rs/nervous_system/agent/Cargo.toml +++ b/rs/nervous_system/agent/Cargo.toml @@ -17,6 +17,7 @@ ic-nns-constants = { path = "../../nns/constants" } ic-sns-wasm = { path = "../../nns/sns-wasm" } ic-sns-governance = { path = "../../sns/governance" } pocket-ic = { path = "../../../packages/pocket-ic" } +ic-sns-root = { path = "../../sns/root" } serde = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } diff --git a/rs/nervous_system/agent/src/nns/sns_wasm.rs b/rs/nervous_system/agent/src/nns/sns_wasm.rs index 9018d8359b33..c981538e5216 100644 --- a/rs/nervous_system/agent/src/nns/sns_wasm.rs +++ b/rs/nervous_system/agent/src/nns/sns_wasm.rs @@ -15,7 +15,7 @@ use tokio::process::Command; use crate::CallCanisters; -pub async fn query_sns_upgrade_steps( +pub async fn query_mainline_sns_upgrade_steps( agent: &C, ) -> Result { let request = ListUpgradeStepsRequest { diff --git a/rs/nervous_system/agent/src/sns/governance.rs b/rs/nervous_system/agent/src/sns/governance.rs index 6efb4013af46..9d5e6e6ced50 100644 --- a/rs/nervous_system/agent/src/sns/governance.rs +++ b/rs/nervous_system/agent/src/sns/governance.rs @@ -1,6 +1,9 @@ use crate::CallCanisters; use ic_base_types::PrincipalId; -use ic_sns_governance::pb::v1::{GetMetadataRequest, GetMetadataResponse}; +use ic_sns_governance::pb::v1::{ + GetMetadataRequest, GetMetadataResponse, GetRunningSnsVersionRequest, + GetRunningSnsVersionResponse, +}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize)] @@ -15,4 +18,13 @@ impl GovernanceCanister { ) -> Result { agent.call(self.canister_id, GetMetadataRequest {}).await } + + pub async fn version( + &self, + agent: &C, + ) -> Result { + agent + .call(self.canister_id, GetRunningSnsVersionRequest {}) + .await + } } diff --git a/rs/nervous_system/agent/src/sns/mod.rs b/rs/nervous_system/agent/src/sns/mod.rs index 8c169fc689d4..f59d882f9245 100644 --- a/rs/nervous_system/agent/src/sns/mod.rs +++ b/rs/nervous_system/agent/src/sns/mod.rs @@ -4,7 +4,13 @@ pub mod ledger; pub mod root; pub mod swap; +use anyhow::Result; +use ic_nns_constants::SNS_WASM_CANISTER_ID; +use ic_sns_wasm::pb::v1::{ListUpgradeStepsRequest, ListUpgradeStepsResponse, SnsVersion}; use serde::{Deserialize, Serialize}; + +use crate::CallCanisters; + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Sns { pub ledger: ledger::LedgerCanister, @@ -14,6 +20,23 @@ pub struct Sns { pub root: root::RootCanister, } +impl Sns { + pub async fn remaining_upgrade_steps( + &self, + agent: &C, + ) -> Result { + let version = self.governance.version(agent).await?; + let list_upgrade_steps_request = ListUpgradeStepsRequest { + limit: 0, + sns_governance_canister_id: Some(self.governance.canister_id), + starting_at: version.deployed_version.map(SnsVersion::from), + }; + agent + .call(SNS_WASM_CANISTER_ID, list_upgrade_steps_request) + .await + } +} + impl TryFrom for Sns { type Error = String; diff --git a/rs/nervous_system/agent/src/sns/root.rs b/rs/nervous_system/agent/src/sns/root.rs index 7ac5651cb1c8..a1aa270b9bf7 100644 --- a/rs/nervous_system/agent/src/sns/root.rs +++ b/rs/nervous_system/agent/src/sns/root.rs @@ -1,7 +1,26 @@ use ic_base_types::PrincipalId; +use ic_sns_root::{GetSnsCanistersSummaryRequest, GetSnsCanistersSummaryResponse}; use serde::{Deserialize, Serialize}; +use crate::CallCanisters; + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct RootCanister { pub canister_id: PrincipalId, } + +impl RootCanister { + pub async fn sns_canisters_summary( + &self, + agent: &C, + ) -> Result { + agent + .call( + self.canister_id, + GetSnsCanistersSummaryRequest { + update_canister_list: None, + }, + ) + .await + } +} diff --git a/rs/nervous_system/tools/sync-with-released-nevous-system-wasms/src/main.rs b/rs/nervous_system/tools/sync-with-released-nevous-system-wasms/src/main.rs index 03f09aa07f1d..cda5d8bd66ac 100644 --- a/rs/nervous_system/tools/sync-with-released-nevous-system-wasms/src/main.rs +++ b/rs/nervous_system/tools/sync-with-released-nevous-system-wasms/src/main.rs @@ -83,7 +83,7 @@ async fn main() -> Result<()> { .into_iter() .collect::>>()?; - let sns_upgrade_steps = sns_wasm::query_sns_upgrade_steps(&agent).await?; + let sns_upgrade_steps = sns_wasm::query_mainline_sns_upgrade_steps(&agent).await?; let latest_sns_version = &sns_upgrade_steps .steps .last() diff --git a/rs/sns/cli/src/lib.rs b/rs/sns/cli/src/lib.rs index 5cf633484d50..fd5f82d58c28 100644 --- a/rs/sns/cli/src/lib.rs +++ b/rs/sns/cli/src/lib.rs @@ -1,5 +1,5 @@ use crate::{ - deploy::DirectSnsDeployerForTests, init_config_file::InitConfigFileArgs, + deploy::DirectSnsDeployerForTests, health::HealthArgs, init_config_file::InitConfigFileArgs, neuron_id_to_candid_subaccount::NeuronIdToCandidSubaccountArgs, prepare_canisters::PrepareCanistersArgs, propose::ProposeArgs, }; @@ -30,6 +30,7 @@ use std::{ use tempfile::NamedTempFile; pub mod deploy; +pub mod health; pub mod init_config_file; pub mod list; pub mod neuron_id_to_candid_subaccount; @@ -77,6 +78,8 @@ pub enum SubCommand { NeuronIdToCandidSubaccount(NeuronIdToCandidSubaccountArgs), /// List SNSes List(list::ListArgs), + /// Check SNSes for warnings and errors. + Health(HealthArgs), } impl CliArgs { diff --git a/rs/sns/cli/src/list.rs b/rs/sns/cli/src/list.rs index 3548a6c91867..3985d5c49c6f 100644 --- a/rs/sns/cli/src/list.rs +++ b/rs/sns/cli/src/list.rs @@ -1,11 +1,9 @@ use crate::table::{as_table, TableRow}; +use crate::utils::{get_snses_with_metadata, SnsWithMetadata}; use anyhow::Result; use clap::Parser; -use futures::{stream, StreamExt}; use ic_agent::Agent; -use ic_nervous_system_agent::{nns::sns_wasm, sns::Sns}; -use itertools::Itertools; -use serde::{Deserialize, Serialize}; +use ic_nervous_system_agent::nns::sns_wasm; #[derive(Debug, Parser)] pub struct ListArgs { @@ -14,12 +12,6 @@ pub struct ListArgs { json: bool, } -#[derive(Debug, Serialize, Deserialize)] -struct SnsWithMetadata { - name: String, - sns: Sns, -} - impl TableRow for SnsWithMetadata { fn column_names() -> Vec<&'static str> { vec!["name", "ledger", "governance", "index", "swap", "root"] @@ -38,24 +30,10 @@ impl TableRow for SnsWithMetadata { } pub async fn exec(args: ListArgs, agent: &Agent) -> Result<()> { + eprintln!("Listing SNSes..."); + let snses = sns_wasm::list_deployed_snses(agent).await?; - let snses_with_metadata = stream::iter(snses) - .map(|sns| async move { - let metadata = sns.governance.metadata(agent).await?; - Ok((sns, metadata)) - }) - .buffer_unordered(10) // Do up to 10 requests at a time in parallel - .collect::>>() - .await; - let snses_with_metadata = snses_with_metadata - .into_iter() - .filter_map(Result::ok) - .map(|(sns, metadata)| { - let name = metadata.name.unwrap_or_else(|| "Unknown".to_string()); - SnsWithMetadata { name, sns } - }) - .sorted_by(|a, b| a.name.cmp(&b.name)) - .collect::>(); + let snses_with_metadata = get_snses_with_metadata(agent, snses).await; let output = if args.json { serde_json::to_string(&snses_with_metadata).unwrap() diff --git a/rs/sns/cli/src/main.rs b/rs/sns/cli/src/main.rs index c57760132e9f..564cd6389910 100644 --- a/rs/sns/cli/src/main.rs +++ b/rs/sns/cli/src/main.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Result}; use clap::Parser; use ic_sns_cli::{ - add_sns_wasm_for_tests, deploy_testflight, init_config_file, list, + add_sns_wasm_for_tests, deploy_testflight, health, init_config_file, list, neuron_id_to_candid_subaccount, prepare_canisters, propose, CliArgs, SubCommand, }; @@ -27,5 +27,6 @@ async fn main() -> Result<()> { SubCommand::Propose(args) => propose::exec(args), SubCommand::NeuronIdToCandidSubaccount(args) => neuron_id_to_candid_subaccount::exec(args), SubCommand::List(args) => list::exec(args, &agent).await, + SubCommand::Health(args) => health::exec(args, &agent).await, } } diff --git a/rs/sns/cli/src/utils.rs b/rs/sns/cli/src/utils.rs index 6cc7a3984414..cc2782b123ad 100644 --- a/rs/sns/cli/src/utils.rs +++ b/rs/sns/cli/src/utils.rs @@ -1,5 +1,9 @@ use anyhow::{anyhow, Result}; +use futures::{stream, StreamExt}; use ic_agent::Agent; +use ic_nervous_system_agent::sns::Sns; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; fn get_agent(ic_url: &str) -> Result { Agent::builder() @@ -13,3 +17,32 @@ pub fn get_mainnet_agent() -> Result { let ic_url = "https://ic0.app/"; get_agent(ic_url) } + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct SnsWithMetadata { + pub(crate) name: String, + pub(crate) sns: Sns, +} + +pub(crate) async fn get_snses_with_metadata( + agent: &Agent, + snses: Vec, +) -> Vec { + let snses_with_metadata = stream::iter(snses) + .map(|sns| async move { + let metadata = sns.governance.metadata(agent).await?; + Ok((sns, metadata)) + }) + .buffer_unordered(10) // Do up to 10 requests at a time in parallel + .collect::>>() + .await; + snses_with_metadata + .into_iter() + .filter_map(Result::ok) + .map(|(sns, metadata)| { + let name = metadata.name.unwrap_or_else(|| "Unknown".to_string()); + SnsWithMetadata { name, sns } + }) + .sorted_by(|a, b| a.name.cmp(&b.name)) + .collect::>() +} diff --git a/rs/sns/governance/src/request_impls.rs b/rs/sns/governance/src/request_impls.rs index 0fd4e810f853..db59152ce16e 100644 --- a/rs/sns/governance/src/request_impls.rs +++ b/rs/sns/governance/src/request_impls.rs @@ -4,9 +4,9 @@ use crate::pb::v1::{ ClaimSwapNeuronsRequest, ClaimSwapNeuronsResponse, FailStuckUpgradeInProgressRequest, FailStuckUpgradeInProgressResponse, GetMaturityModulationRequest, GetMaturityModulationResponse, GetMetadataRequest, GetMetadataResponse, GetMode, - GetModeResponse, GetNeuronResponse, GetProposalResponse, GetSnsInitializationParametersRequest, - GetSnsInitializationParametersResponse, ListNeuronsResponse, ListProposalsResponse, - ManageNeuronResponse, + GetModeResponse, GetNeuronResponse, GetProposalResponse, GetRunningSnsVersionResponse, + GetSnsInitializationParametersRequest, GetSnsInitializationParametersResponse, + ListNeuronsResponse, ListProposalsResponse, ManageNeuronResponse, }; impl Request for ClaimSwapNeuronsRequest { @@ -74,3 +74,9 @@ impl Request for crate::pb::v1::ManageNeuron { const METHOD: &'static str = "manage_neuron"; const UPDATE: bool = true; } + +impl Request for crate::pb::v1::GetRunningSnsVersionRequest { + type Response = GetRunningSnsVersionResponse; + const METHOD: &'static str = "get_running_sns_version"; + const UPDATE: bool = true; +} diff --git a/rs/sns/root/src/lib.rs b/rs/sns/root/src/lib.rs index 0e6ad6bc3f37..cd8de1434aba 100644 --- a/rs/sns/root/src/lib.rs +++ b/rs/sns/root/src/lib.rs @@ -28,10 +28,12 @@ use std::{ fmt::Write, thread::LocalKey, }; +use types::SnsCanisterType; pub use icrc_ledger_types::icrc3::archive::ArchiveInfo; pub mod logs; pub mod pb; +mod request_impls; pub mod types; // The number of dapp canisters that can be registered with the SNS Root @@ -119,6 +121,39 @@ impl GetSnsCanistersSummaryResponse { } } +impl IntoIterator for GetSnsCanistersSummaryResponse { + type Item = (Option, SnsCanisterType); + + // Using Box> because the type is very long otherwise. + // But this could be changed to a more specific type. + type IntoIter = Box>; + + fn into_iter(self) -> Self::IntoIter { + let canisters = [ + (self.root, SnsCanisterType::Root), + (self.governance, SnsCanisterType::Governance), + (self.ledger, SnsCanisterType::Ledger), + (self.swap, SnsCanisterType::Swap), + (self.index, SnsCanisterType::Index), + ]; + + Box::new( + canisters + .into_iter() + .chain( + self.dapps + .into_iter() + .map(|d| (Some(d), SnsCanisterType::Dapp)), + ) + .chain( + self.archives + .into_iter() + .map(|a| (Some(a), SnsCanisterType::Archive)), + ), + ) + } +} + #[derive(Clone, Eq, PartialEq, Debug, Default, candid::CandidType, candid::Deserialize)] pub struct CanisterSummary { pub canister_id: Option, diff --git a/rs/sns/root/src/request_impls.rs b/rs/sns/root/src/request_impls.rs new file mode 100644 index 000000000000..12acd0d45b4e --- /dev/null +++ b/rs/sns/root/src/request_impls.rs @@ -0,0 +1,8 @@ +use crate::{GetSnsCanistersSummaryRequest, GetSnsCanistersSummaryResponse}; +use ic_nervous_system_clients::Request; + +impl Request for GetSnsCanistersSummaryRequest { + type Response = GetSnsCanistersSummaryResponse; + const METHOD: &'static str = "get_sns_canisters_summary"; + const UPDATE: bool = true; +} diff --git a/rs/sns/root/src/types.rs b/rs/sns/root/src/types.rs index 40dae10aa405..cd7cb0192a19 100644 --- a/rs/sns/root/src/types.rs +++ b/rs/sns/root/src/types.rs @@ -1,5 +1,9 @@ +use std::fmt::{self, Display, Formatter}; + use async_trait::async_trait; use ic_base_types::CanisterId; +use serde::{Deserialize, Serialize}; + /// A general trait for the environment in which governance is running. #[async_trait] pub trait Environment: Send + Sync { @@ -20,3 +24,29 @@ pub trait Environment: Send + Sync { arg: Vec, ) -> Result, (/* error_code: */ i32, /* message: */ String)>; } + +/// Different from the SnsCanisterType in SNS-W because it includes Dap +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub enum SnsCanisterType { + Root, + Governance, + Ledger, + Swap, + Archive, + Index, + Dapp, +} + +impl Display for SnsCanisterType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + SnsCanisterType::Root => write!(f, "root"), + SnsCanisterType::Governance => write!(f, "governance"), + SnsCanisterType::Ledger => write!(f, "ledger"), + SnsCanisterType::Swap => write!(f, "swap"), + SnsCanisterType::Dapp => write!(f, "dapp"), + SnsCanisterType::Archive => write!(f, "archive"), + SnsCanisterType::Index => write!(f, "index"), + } + } +}