Skip to content

Commit

Permalink
feat(sns-cli): Add sns-lint command
Browse files Browse the repository at this point in the history
  • Loading branch information
anchpop committed Sep 26, 2024
1 parent 09e7929 commit dd85a6d
Show file tree
Hide file tree
Showing 17 changed files with 281 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rs/nervous_system/agent/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions rs/nervous_system/agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion rs/nervous_system/agent/src/nns/sns_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use tokio::process::Command;

use crate::CallCanisters;

pub async fn query_sns_upgrade_steps<C: CallCanisters>(
pub async fn query_mainline_sns_upgrade_steps<C: CallCanisters>(
agent: &C,
) -> Result<ListUpgradeStepsResponse, C::Error> {
let request = ListUpgradeStepsRequest {
Expand Down
14 changes: 13 additions & 1 deletion rs/nervous_system/agent/src/sns/governance.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -15,4 +18,13 @@ impl GovernanceCanister {
) -> Result<GetMetadataResponse, C::Error> {
agent.call(self.canister_id, GetMetadataRequest {}).await
}

pub async fn version<C: CallCanisters>(
&self,
agent: &C,
) -> Result<GetRunningSnsVersionResponse, C::Error> {
agent
.call(self.canister_id, GetRunningSnsVersionRequest {})
.await
}
}
23 changes: 23 additions & 0 deletions rs/nervous_system/agent/src/sns/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -14,6 +20,23 @@ pub struct Sns {
pub root: root::RootCanister,
}

impl Sns {
pub async fn remaining_upgrade_steps<C: CallCanisters>(
&self,
agent: &C,
) -> Result<ListUpgradeStepsResponse, C::Error> {
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<ic_sns_wasm::pb::v1::DeployedSns> for Sns {
type Error = String;

Expand Down
19 changes: 19 additions & 0 deletions rs/nervous_system/agent/src/sns/root.rs
Original file line number Diff line number Diff line change
@@ -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<C: CallCanisters>(
&self,
agent: &C,
) -> Result<GetSnsCanistersSummaryResponse, C::Error> {
agent
.call(
self.canister_id,
GetSnsCanistersSummaryRequest {
update_canister_list: None,
},
)
.await
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async fn main() -> Result<()> {
.into_iter()
.collect::<Result<Vec<CanisterUpdate>>>()?;

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()
Expand Down
1 change: 1 addition & 0 deletions rs/nns/sns-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ pub mod pb;
mod request_impls;
pub mod sns_wasm;
pub mod stable_memory;
mod types;
pub mod wasm_metadata;
14 changes: 14 additions & 0 deletions rs/nns/sns-wasm/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::pb::v1::{ListUpgradeStepsResponse, SnsVersion};

impl ListUpgradeStepsResponse {
pub fn version_number(&self, v: SnsVersion) -> Option<usize> {
self.steps
.iter()
.position(|x| x.version == Some(v.clone()))
.map(|x| x + 1)
}

pub fn latest_version_number(&self) -> usize {
self.steps.len()
}
}
5 changes: 4 additions & 1 deletion rs/sns/cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
deploy::DirectSnsDeployerForTests, init_config_file::InitConfigFileArgs,
deploy::DirectSnsDeployerForTests, init_config_file::InitConfigFileArgs, lint::LintArgs,
neuron_id_to_candid_subaccount::NeuronIdToCandidSubaccountArgs,
prepare_canisters::PrepareCanistersArgs, propose::ProposeArgs,
};
Expand Down Expand Up @@ -31,6 +31,7 @@ use tempfile::NamedTempFile;

pub mod deploy;
pub mod init_config_file;
pub mod lint;
pub mod list;
pub mod neuron_id_to_candid_subaccount;
pub mod prepare_canisters;
Expand Down Expand Up @@ -77,6 +78,8 @@ pub enum SubCommand {
NeuronIdToCandidSubaccount(NeuronIdToCandidSubaccountArgs),
/// List SNSes
List(list::ListArgs),
/// Check SNSes for warnings and errors.
Lint(LintArgs),
}

impl CliArgs {
Expand Down
180 changes: 180 additions & 0 deletions rs/sns/cli/src/lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use crate::table::TableRow;
use anyhow::{anyhow, Result};
use clap::Parser;
use futures::{stream, StreamExt};
use ic_agent::Agent;
use ic_nervous_system_agent::nns::sns_wasm;
use itertools::Itertools;

/// The arguments used to configure the lint command
#[derive(Debug, Parser)]
pub struct LintArgs {}

struct SnsLintInfo {
name: String,
high_memory_consumption: Vec<(String, u64)>,
low_cycles: Vec<(String, u64)>,
num_remaining_upgrade_steps: usize,
}

impl TableRow for SnsLintInfo {
fn column_names() -> Vec<&'static str> {
vec!["Name", "Memory", "Cycles", "Upgrades Remaining"]
}

fn column_values(&self) -> Vec<String> {
let memory_consumption = self
.high_memory_consumption
.iter()
.map(|(canister_type, memory_consumption)| {
format!(
"{canister_type} ({:.2} GiB)",
*memory_consumption as f64 / 1024.0 / 1024.0 / 1024.0
)
})
.join(", ");
let memory_consumption = if !memory_consumption.is_empty() {
format!("❌ {memory_consumption}")
} else {
"👍".to_string()
};
let low_cycles = self
.low_cycles
.iter()
.map(|(canister_type, cycles)| {
format!(
"{canister_type} ({:.2} TC)",
*cycles as f64 / 1000.0 / 1000.0 / 1000.0 / 1000.0
)
})
.join(", ");
let low_cycles = if !low_cycles.is_empty() {
format!("❌ {low_cycles}")
} else {
"👍".to_string()
};
vec![
self.name.clone(),
memory_consumption,
low_cycles,
format!("{}", self.num_remaining_upgrade_steps),
]
}
}

pub async fn exec(_args: LintArgs, agent: &Agent) -> Result<()> {
eprintln!("Linting SNSes...");

let snses = sns_wasm::list_deployed_snses(agent).await?;
let num_total_snses = snses.len();
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::<Vec<anyhow::Result<_>>>()
.await;
let snses_with_metadata = snses_with_metadata
.into_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();

let num_snses_with_metadata = snses_with_metadata.len();

let lint_info: Vec<SnsLintInfo> = stream::iter(snses_with_metadata)
.map(|(sns, metadata)| async move {
let summary = sns.root.sns_canisters_summary(agent).await?;
let name = metadata.name.ok_or(anyhow!("SNS has no name"))?;

let governance_summary = summary.governance.ok_or(anyhow!(
"SNS {name} canister summary is missing `governance`"
))?;
let governance_status = governance_summary
.status
.ok_or(anyhow!("SNS {name} `governance` has no status"))?;

let root_summary = summary
.root
.ok_or(anyhow!("SNS {name} canister summary is missing `root`"))?;
let root_status = root_summary
.status
.ok_or(anyhow!("SNS {name} `root` has no status"))?;

let swap_summary = summary
.swap
.ok_or(anyhow!("SNS {name} canister summary is missing `swap`"))?;
let swap_status = swap_summary
.status
.ok_or(anyhow!("SNS {name} `swap` has no status"))?;

let high_memory_consumption = {
let governance_memory_consumption =
{ u64::try_from(governance_status.memory_size.0).unwrap() };

let root_memory_consumption = { u64::try_from(root_status.memory_size.0).unwrap() };

let swap_memory_consumption = { u64::try_from(swap_status.memory_size.0).unwrap() };

[
("governance", governance_memory_consumption),
("root", root_memory_consumption),
("swap", swap_memory_consumption),
]
.iter()
.filter(|(_, memory_consumption)| {
(*memory_consumption as f64) > 2.5 * 1024.0 * 1024.0 * 1024.0
})
.map(|(canister, memory_consumption)| (canister.to_string(), *memory_consumption))
.collect::<Vec<_>>()
};

let low_cycles = {
let governance_cycles = { u64::try_from(governance_status.cycles.0).unwrap() };

let root_cycles = { u64::try_from(root_status.cycles.0).unwrap() };

let swap_cycles = { u64::try_from(swap_status.cycles.0).unwrap() };

[
("governance", governance_cycles),
("root", root_cycles),
("swap", swap_cycles),
]
.iter()
.filter(|(_, cycles)| (*cycles as f64) < 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)
.map(|(canister, cycles)| (canister.to_string(), *cycles))
.collect::<Vec<_>>()
};

let num_remaining_upgrade_steps =
sns.remaining_upgrade_steps(agent).await?.steps.len() - 1;

Result::<SnsLintInfo, anyhow::Error>::Ok(SnsLintInfo {
name,
high_memory_consumption,
low_cycles,
num_remaining_upgrade_steps,
})
})
.buffer_unordered(10)
.collect::<Vec<Result<_>>>()
.await
.into_iter()
.inspect(|result| match result {
Err(e) => println!("Error: {}", e),
_ => {}
})
.filter_map(Result::ok)
.sorted_by(|a, b| a.name.cmp(&b.name))
.collect::<Vec<_>>();

let lint_info_table = crate::table::as_table(lint_info.as_ref());
println!("{}", lint_info_table);
eprintln!(
"Out of {num_total_snses} SNSes, {num_snses_with_metadata} had metadata and I linted {num_linted} of them.",
num_linted = lint_info.len()
);

Ok(())
}
2 changes: 2 additions & 0 deletions rs/sns/cli/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ 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 {
Expand Down
3 changes: 2 additions & 1 deletion rs/sns/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, init_config_file, lint, list,
neuron_id_to_candid_subaccount, prepare_canisters, propose, CliArgs, SubCommand,
};

Expand All @@ -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::Lint(args) => lint::exec(args, &agent).await,
}
}
Loading

0 comments on commit dd85a6d

Please sign in to comment.