From 649719de5edca8064ca380e58c464874bfa59ca9 Mon Sep 17 00:00:00 2001 From: Nikola Milosavljevic <73236646+NikolaMilosa@users.noreply.github.com> Date: Fri, 17 May 2024 13:00:55 +0200 Subject: [PATCH] feat(dre): Better listing proposals (#382) * first iteration * implementing first working solution * less queries for filtering * implemented v1 * better proposal filterin --- Cargo.Bazel.lock | 211 ++++++++++++++++++++---- Cargo.lock | 72 +++++--- Cargo.toml | 4 + rs/cli/Cargo.toml | 5 + rs/cli/src/cli.rs | 196 ++++++++++++++++++++++ rs/cli/src/general.rs | 370 +++++++++++++++++++++++++++++++++++++++++- rs/cli/src/main.rs | 5 +- 7 files changed, 810 insertions(+), 53 deletions(-) diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index bab6740b..6487b555 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "c00bb631ec3542a209120b2d90500a100e415b96d15634e49da36ef83acb4c9c", + "checksum": "b76ca6e07b594e6c690a4f7a63269eecaae962d8a2bcb5976e90b3420b116d04", "crates": { "actix-codec 0.5.2": { "name": "actix-codec", @@ -6228,7 +6228,7 @@ "target": "pretty_env_logger" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -11704,6 +11704,10 @@ "id": "cryptoki 0.3.1", "target": "cryptoki" }, + { + "id": "cycles-minting-canister 0.9.0", + "target": "cycles_minting_canister" + }, { "id": "dialoguer 0.11.0", "target": "dialoguer" @@ -11748,6 +11752,14 @@ "id": "ic-interfaces-registry 0.9.0", "target": "ic_interfaces_registry" }, + { + "id": "ic-nervous-system-clients 0.0.1", + "target": "ic_nervous_system_clients" + }, + { + "id": "ic-nervous-system-root 0.9.0", + "target": "ic_nervous_system_root" + }, { "id": "ic-nns-common 0.9.0", "target": "ic_nns_common" @@ -11776,6 +11788,10 @@ "id": "ic-registry-subnet-type 0.9.0", "target": "ic_registry_subnet_type" }, + { + "id": "ic-sns-wasm 1.0.0", + "target": "ic_sns_wasm" + }, { "id": "ic-sys 0.9.0", "target": "ic_sys" @@ -11809,7 +11825,7 @@ "target": "registry_canister" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -11910,6 +11926,10 @@ "id": "ic-base-types 0.9.0", "target": "ic_base_types" }, + { + "id": "ic-nns-governance 0.9.0", + "target": "ic_nns_governance" + }, { "id": "url 2.5.0", "target": "url" @@ -16850,7 +16870,7 @@ "target": "pin_project_lite" }, { - "id": "socket2 0.5.6", + "id": "socket2 0.4.10", "target": "socket2" }, { @@ -22194,7 +22214,7 @@ "target": "registry_canister" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -22409,7 +22429,7 @@ "target": "registry_canister" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -29747,7 +29767,7 @@ "target": "pretty_env_logger" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -30756,7 +30776,7 @@ "target": "regex" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -31299,7 +31319,7 @@ "target": "rand" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -37796,13 +37816,13 @@ }, "license": "MIT OR Apache-2.0" }, - "reqwest 0.12.2": { + "reqwest 0.12.4": { "name": "reqwest", - "version": "0.12.2", + "version": "0.12.4", "repository": { "Http": { - "url": "https://static.crates.io/crates/reqwest/0.12.2/download", - "sha256": "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" + "url": "https://static.crates.io/crates/reqwest/0.12.4/download", + "sha256": "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" } }, "targets": [ @@ -37837,7 +37857,7 @@ "deps": { "common": [ { - "id": "base64 0.21.7", + "id": "base64 0.22.0", "target": "base64" }, { @@ -37941,7 +37961,7 @@ "target": "pin_project_lite" }, { - "id": "rustls-pemfile 1.0.4", + "id": "rustls-pemfile 2.1.2", "target": "rustls_pemfile" }, { @@ -37979,14 +37999,14 @@ ], "cfg(windows)": [ { - "id": "winreg 0.50.0", + "id": "winreg 0.52.0", "target": "winreg" } ] } }, "edition": "2021", - "version": "0.12.2" + "version": "0.12.4" }, "license": "MIT OR Apache-2.0" }, @@ -38395,7 +38415,7 @@ "target": "registry_canister" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -39342,6 +39362,94 @@ }, "license": "Apache-2.0 OR ISC OR MIT" }, + "rustls-pemfile 2.1.2": { + "name": "rustls-pemfile", + "version": "2.1.2", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustls-pemfile/2.1.2/download", + "sha256": "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rustls_pemfile", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "rustls_pemfile", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "base64 0.22.0", + "target": "base64" + }, + { + "id": "rustls-pki-types 1.7.0", + "target": "rustls_pki_types", + "alias": "pki_types" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "2.1.2" + }, + "license": "Apache-2.0 OR ISC OR MIT" + }, + "rustls-pki-types 1.7.0": { + "name": "rustls-pki-types", + "version": "1.7.0", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustls-pki-types/1.7.0/download", + "sha256": "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rustls_pki_types", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "rustls_pki_types", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.7.0" + }, + "license": "MIT OR Apache-2.0" + }, "rustls-webpki 0.101.7": { "name": "rustls-webpki", "version": "0.101.7", @@ -41511,7 +41619,7 @@ "target": "registry_canister" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -42000,7 +42108,7 @@ "target": "regex" }, { - "id": "reqwest 0.12.2", + "id": "reqwest 0.12.4", "target": "reqwest" }, { @@ -47561,7 +47669,9 @@ "winbase", "wincon", "winerror", - "winnt" + "winnt", + "ws2ipdef", + "ws2tcpip" ], "selects": {} }, @@ -49420,6 +49530,49 @@ }, "license": "MIT" }, + "winreg 0.52.0": { + "name": "winreg", + "version": "0.52.0", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/winreg/0.52.0/download", + "sha256": "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" + } + }, + "targets": [ + { + "Library": { + "crate_name": "winreg", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "winreg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cfg-if 1.0.0", + "target": "cfg_if" + }, + { + "id": "windows-sys 0.48.0", + "target": "windows_sys" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.52.0" + }, + "license": "MIT" + }, "wiremock 0.6.0": { "name": "wiremock", "version": "0.6.0", @@ -50632,7 +50785,7 @@ "target": "build_script_build" }, { - "id": "zstd-sys 2.0.9+zstd.1.5.5", + "id": "zstd-sys 2.0.10+zstd.1.5.6", "target": "zstd_sys" } ], @@ -50648,7 +50801,7 @@ "link_deps": { "common": [ { - "id": "zstd-sys 2.0.9+zstd.1.5.5", + "id": "zstd-sys 2.0.10+zstd.1.5.6", "target": "zstd_sys" } ], @@ -50657,13 +50810,13 @@ }, "license": "MIT/Apache-2.0" }, - "zstd-sys 2.0.9+zstd.1.5.5": { + "zstd-sys 2.0.10+zstd.1.5.6": { "name": "zstd-sys", - "version": "2.0.9+zstd.1.5.5", + "version": "2.0.10+zstd.1.5.6", "repository": { "Http": { - "url": "https://static.crates.io/crates/zstd-sys/2.0.9+zstd.1.5.5/download", - "sha256": "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" + "url": "https://static.crates.io/crates/zstd-sys/2.0.10+zstd.1.5.6/download", + "sha256": "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" } }, "targets": [ @@ -50702,14 +50855,14 @@ "deps": { "common": [ { - "id": "zstd-sys 2.0.9+zstd.1.5.5", + "id": "zstd-sys 2.0.10+zstd.1.5.6", "target": "build_script_build" } ], "selects": {} }, "edition": "2018", - "version": "2.0.9+zstd.1.5.5" + "version": "2.0.10+zstd.1.5.6" }, "build_script_attrs": { "data_glob": [ diff --git a/Cargo.lock b/Cargo.lock index 0f131e0f..ca3b97cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1237,7 +1237,7 @@ dependencies = [ "humantime", "log", "pretty_env_logger", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde", "serde_json", "tokio", @@ -2320,6 +2320,7 @@ dependencies = [ "clap_complete", "colored", "cryptoki", + "cycles-minting-canister", "decentralization", "dialoguer", "dirs", @@ -2334,6 +2335,8 @@ dependencies = [ "ic-interfaces-registry", "ic-management-backend", "ic-management-types", + "ic-nervous-system-clients", + "ic-nervous-system-root", "ic-nns-common", "ic-nns-constants", "ic-nns-governance", @@ -2341,6 +2344,7 @@ dependencies = [ "ic-registry-keys", "ic-registry-local-registry", "ic-registry-subnet-type", + "ic-sns-wasm", "ic-sys", "itertools 0.12.1", "keyring", @@ -2349,7 +2353,7 @@ dependencies = [ "prost", "regex", "registry-canister", - "reqwest 0.12.2", + "reqwest 0.12.4", "self_update", "serde", "serde_json", @@ -3353,7 +3357,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.6", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -4476,7 +4480,7 @@ dependencies = [ "prometheus-http-query", "regex", "registry-canister", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde", "serde_json", "serde_yaml", @@ -4519,7 +4523,7 @@ dependencies = [ "ic-registry-subnet-type", "ic-types", "registry-canister", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde", "serde_json", "strum 0.26.2", @@ -5980,7 +5984,7 @@ dependencies = [ "clap 4.5.4", "log", "pretty_env_logger", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde", "serde_json", "tokio", @@ -6181,7 +6185,7 @@ dependencies = [ "ic-async-utils", "multiservice-discovery-shared", "regex", - "reqwest 0.12.2", + "reqwest 0.12.4", "service-discovery", "slog", "slog-async", @@ -6296,7 +6300,7 @@ dependencies = [ "ic-types", "pretty_assertions", "rand", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde", "serde_json", "serde_yaml", @@ -7496,7 +7500,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -7513,16 +7517,16 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "winreg", + "winreg 0.50.0", ] [[package]] name = "reqwest" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "bytes", "encoding_rs", "futures-core", @@ -7542,7 +7546,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", @@ -7555,7 +7559,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -7646,7 +7650,7 @@ dependencies = [ "pretty_assertions", "prometheus-http-query", "registry-canister", - "reqwest 0.12.2", + "reqwest 0.12.4", "rstest", "serde", "serde_yaml", @@ -7798,7 +7802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "schannel", "security-framework", ] @@ -7812,6 +7816,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -8247,7 +8267,7 @@ dependencies = [ "log", "regex", "registry-canister", - "reqwest 0.12.2", + "reqwest 0.12.4", "retry", "serde", "serde_json", @@ -8340,7 +8360,7 @@ dependencies = [ "ic-async-utils", "multiservice-discovery-shared", "regex", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde_json", "slog", "slog-async", @@ -9647,6 +9667,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wiremock" version = "0.6.0" @@ -9907,9 +9937,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 172467af..6523971b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,10 @@ ic-registry-subnet-type = { git = "https://github.com/dfinity/ic.git", rev = "5b ic-registry-transport = { git = "https://github.com/dfinity/ic.git", rev = "5ba1412f9175d987661ae3c0d8dbd1ac3e092b7d" } ic-sys = { git = "https://github.com/dfinity/ic.git", rev = "5ba1412f9175d987661ae3c0d8dbd1ac3e092b7d" } ic-types = { git = "https://github.com/dfinity/ic.git", rev = "5ba1412f9175d987661ae3c0d8dbd1ac3e092b7d" } +ic-nervous-system-root = { git = "https://github.com/dfinity/ic.git", rev = "5ba1412f9175d987661ae3c0d8dbd1ac3e092b7d" } +ic-nervous-system-clients = { git = "https://github.com/dfinity/ic.git", rev = "5ba1412f9175d987661ae3c0d8dbd1ac3e092b7d" } +ic-sns-wasm = { git = "https://github.com/dfinity/ic.git", rev = "5ba1412f9175d987661ae3c0d8dbd1ac3e092b7d" } +cycles-minting-canister = { git = "https://github.com/dfinity/ic.git", rev = "5ba1412f9175d987661ae3c0d8dbd1ac3e092b7d" } ic-utils = "0.34.0" include_dir = "0.7.3" itertools = "0.12.0" diff --git a/rs/cli/Cargo.toml b/rs/cli/Cargo.toml index 29ae3eff..8f173285 100644 --- a/rs/cli/Cargo.toml +++ b/rs/cli/Cargo.toml @@ -61,6 +61,10 @@ tokio = { workspace = true } url = { workspace = true } tempfile = "3.10.0" self_update = { version = "0.39.0", features = [ "archive-tar" ]} +ic-nervous-system-root = { workspace = true } +ic-nervous-system-clients = { workspace = true } +cycles-minting-canister = { workspace = true } +ic-sns-wasm = { workspace = true } [dev-dependencies] wiremock = { workspace = true } @@ -72,6 +76,7 @@ clap-num = { workspace = true } ic-base-types = { workspace = true } ic-management-types = { workspace = true } url = { workspace = true } +ic-nns-governance = { workspace = true } [[bin]] name = "dre" diff --git a/rs/cli/src/cli.rs b/rs/cli/src/cli.rs index cda49796..fe1d952b 100644 --- a/rs/cli/src/cli.rs +++ b/rs/cli/src/cli.rs @@ -428,6 +428,9 @@ pub mod nodes { } pub mod proposals { + use clap::ValueEnum; + use ic_nns_governance::pb::v1::{ProposalStatus as ProposalStatusUpstream, Topic as TopicUpstream}; + use super::*; #[derive(Parser, Clone)] @@ -484,5 +487,198 @@ pub mod proposals { #[clap(long)] omit_large_fields: Option, }, + + /// More user-friendly command for filtering proposals + Filter { + /// Limit on the number of \[ProposalInfo\] to return. If value greater than + /// canister limit is used it will still be fetch the wanted number of proposals. + #[clap(long, default_value = "100")] + limit: u32, + + /// Proposal statuses to include. If not specified will include all proposals. + #[arg(value_enum)] + #[clap(long, aliases = ["status"], short = 's')] + statuses: Vec, + + /// Proposal topics to include. If not specified will include all proposals. + #[arg(value_enum)] + #[clap(long, aliases = ["topic"], short = 't')] + topics: Vec, + }, + } + + #[derive(ValueEnum, Clone, Debug)] + pub enum ProposalStatus { + Unspecified = 0, + /// A decision (adopt/reject) has yet to be made. + Open = 1, + /// The proposal has been rejected. + Rejected = 2, + /// The proposal has been adopted (sometimes also called + /// "accepted"). At this time, either execution as not yet started, + /// or it has but the outcome is not yet known. + Adopted = 3, + /// The proposal was adopted and successfully executed. + Executed = 4, + /// The proposal was adopted, but execution failed. + Failed = 5, + } + + impl From for ProposalStatusUpstream { + fn from(value: ProposalStatus) -> Self { + match value { + ProposalStatus::Unspecified => Self::Unspecified, + ProposalStatus::Open => Self::Open, + ProposalStatus::Rejected => Self::Rejected, + ProposalStatus::Adopted => Self::Adopted, + ProposalStatus::Executed => Self::Executed, + ProposalStatus::Failed => Self::Failed, + } + } + } + + impl From for ProposalStatus { + fn from(value: ProposalStatusUpstream) -> Self { + match value { + ProposalStatusUpstream::Unspecified => Self::Unspecified, + ProposalStatusUpstream::Open => Self::Open, + ProposalStatusUpstream::Rejected => Self::Rejected, + ProposalStatusUpstream::Adopted => Self::Adopted, + ProposalStatusUpstream::Executed => Self::Executed, + ProposalStatusUpstream::Failed => Self::Failed, + } + } + } + + #[derive(ValueEnum, Clone, Debug)] + pub enum Topic { + /// The `Unspecified` topic is used as a fallback when + /// following. That is, if no followees are specified for a given + /// topic, the followees for this topic are used instead. + Unspecified = 0, + /// A special topic by means of which a neuron can be managed by the + /// followees for this topic (in this case, there is no fallback to + /// 'unspecified'). Votes on this topic are not included in the + /// voting history of the neuron (cf., `recent_ballots` in `Neuron`). + /// + /// For proposals on this topic, only followees on the 'neuron + /// management' topic of the neuron that the proposals pertains to + /// are allowed to vote. + /// + /// As the set of eligible voters on this topic is restricted, + /// proposals on this topic have a *short voting period*. + NeuronManagement = 1, + /// All proposals that provide “real time” information about the + /// value of ICP, as measured by an IMF SDR, which allows the NNS to + /// convert ICP to cycles (which power computation) at a rate which + /// keeps their real world cost constant. Votes on this topic are not + /// included in the voting history of the neuron (cf., + /// `recent_ballots` in `Neuron`). + /// + /// Proposals on this topic have a *short voting period* due to their + /// frequency. + ExchangeRate = 2, + /// All proposals that administer network economics, for example, + /// determining what rewards should be paid to node operators. + NetworkEconomics = 3, + /// All proposals that administer governance, for example to freeze + /// malicious canisters that are harming the network. + Governance = 4, + /// All proposals that administer node machines, including, but not + /// limited to, upgrading or configuring the OS, upgrading or + /// configuring the virtual machine framework and upgrading or + /// configuring the node replica software. + NodeAdmin = 5, + /// All proposals that administer network participants, for example, + /// granting and revoking DCIDs (data center identities) or NOIDs + /// (node operator identities). + ParticipantManagement = 6, + /// All proposals that administer network subnets, for example + /// creating new subnets, adding and removing subnet nodes, and + /// splitting subnets. + SubnetManagement = 7, + /// Installing and upgrading “system” canisters that belong to the network. + /// For example, upgrading the NNS. + NetworkCanisterManagement = 8, + /// Proposals that update KYC information for regulatory purposes, + /// for example during the initial Genesis distribution of ICP in the + /// form of neurons. + Kyc = 9, + /// Topic for proposals to reward node providers. + NodeProviderRewards = 10, + /// IC OS upgrade proposals + /// ----------------------- + /// ICP runs on a distributed network of nodes grouped into subnets. Each node runs a stack of + /// operating systems, including HostOS (runs on bare metal) and GuestOS (runs inside HostOS; + /// contains, e.g., the ICP replica process). HostOS and GuestOS are distributed via separate disk + /// images. The umbrella term IC OS refers to the whole stack. + /// + /// The IC OS upgrade process involves two phases, where the first phase is the election of a new + /// IC OS version and the second phase is the deployment of a previously elected IC OS version on + /// all nodes of a subnet or on some number of nodes (including nodes comprising subnets and + /// unassigned nodes). + /// + /// A special case is for API boundary nodes, special nodes that route API requests to a replica + /// of the right subnet. API boundary nodes run a different process than the replica, but their + /// executable is distributed via the same disk image as GuestOS. Therefore, electing a new GuestOS + /// version also results in a new version of boundary node software being elected. + /// + /// Proposals handling the deployment of IC OS to some nodes. It is possible to deploy only + /// the versions of IC OS that are in the set of elected IC OS versions. + IcOsVersionDeployment = 12, + /// Proposals for changing the set of elected IC OS versions. + IcOsVersionElection = 13, + /// Proposals related to SNS and Community Fund. + SnsAndCommunityFund = 14, + /// Proposals related to the management of API Boundary Nodes + ApiBoundaryNodeManagement = 15, + /// Proposals related to the management of API Boundary Nodes + SubnetRental = 16, + } + + impl From for TopicUpstream { + fn from(value: Topic) -> Self { + match value { + Topic::Unspecified => Self::Unspecified, + Topic::NeuronManagement => Self::NeuronManagement, + Topic::ExchangeRate => Self::ExchangeRate, + Topic::NetworkEconomics => Self::NetworkEconomics, + Topic::Governance => Self::Governance, + Topic::NodeAdmin => Self::NodeAdmin, + Topic::ParticipantManagement => Self::ParticipantManagement, + Topic::SubnetManagement => Self::SubnetManagement, + Topic::NetworkCanisterManagement => Self::NetworkCanisterManagement, + Topic::Kyc => Self::Kyc, + Topic::NodeProviderRewards => Self::NodeProviderRewards, + Topic::IcOsVersionDeployment => Self::IcOsVersionDeployment, + Topic::IcOsVersionElection => Self::IcOsVersionElection, + Topic::SnsAndCommunityFund => Self::SnsAndCommunityFund, + Topic::ApiBoundaryNodeManagement => Self::ApiBoundaryNodeManagement, + Topic::SubnetRental => Self::SubnetRental, + } + } + } + + impl From for Topic { + fn from(value: TopicUpstream) -> Self { + match value { + TopicUpstream::Unspecified => Self::Unspecified, + TopicUpstream::NeuronManagement => Self::NeuronManagement, + TopicUpstream::ExchangeRate => Self::ExchangeRate, + TopicUpstream::NetworkEconomics => Self::NetworkEconomics, + TopicUpstream::Governance => Self::Governance, + TopicUpstream::NodeAdmin => Self::NodeAdmin, + TopicUpstream::ParticipantManagement => Self::ParticipantManagement, + TopicUpstream::SubnetManagement => Self::SubnetManagement, + TopicUpstream::NetworkCanisterManagement => Self::NetworkCanisterManagement, + TopicUpstream::Kyc => Self::Kyc, + TopicUpstream::NodeProviderRewards => Self::NodeProviderRewards, + TopicUpstream::IcOsVersionDeployment => Self::IcOsVersionDeployment, + TopicUpstream::IcOsVersionElection => Self::IcOsVersionElection, + TopicUpstream::SnsAndCommunityFund => Self::SnsAndCommunityFund, + TopicUpstream::ApiBoundaryNodeManagement => Self::ApiBoundaryNodeManagement, + TopicUpstream::SubnetRental => Self::SubnetRental, + } + } } } diff --git a/rs/cli/src/general.rs b/rs/cli/src/general.rs index e6c4fd84..9cde0efd 100644 --- a/rs/cli/src/general.rs +++ b/rs/cli/src/general.rs @@ -1,4 +1,47 @@ +use candid::Decode; +use cycles_minting_canister::SetAuthorizedSubnetworkListArgs; use ic_base_types::{CanisterId, PrincipalId}; +use ic_management_types::Network; +use ic_nervous_system_clients::canister_id_record::CanisterIdRecord; +use ic_nervous_system_root::change_canister::{AddCanisterRequest, ChangeCanisterRequest, StopOrStartCanisterRequest}; +use ic_nns_common::{pb::v1::ProposalId, types::UpdateIcpXdrConversionRatePayload}; +use ic_protobuf::registry::{ + dc::v1::AddOrRemoveDataCentersProposalPayload, node_operator::v1::RemoveNodeOperatorsPayload, + node_rewards::v2::UpdateNodeRewardsTableProposalPayload, +}; +use ic_sns_wasm::pb::v1::{ + AddWasmRequest, InsertUpgradePathEntriesRequest, UpdateAllowedPrincipalsRequest, UpdateSnsSubnetListRequest, +}; +use itertools::Itertools; +use registry_canister::mutations::{ + complete_canister_migration::CompleteCanisterMigrationPayload, + do_add_api_boundary_nodes::AddApiBoundaryNodesPayload, + do_add_node_operator::AddNodeOperatorPayload, + do_add_nodes_to_subnet::AddNodesToSubnetPayload, + do_bless_replica_version::BlessReplicaVersionPayload, + do_change_subnet_membership::ChangeSubnetMembershipPayload, + do_create_subnet::CreateSubnetPayload, + do_deploy_guestos_to_all_subnet_nodes::DeployGuestosToAllSubnetNodesPayload, + do_deploy_guestos_to_all_unassigned_nodes::DeployGuestosToAllUnassignedNodesPayload, + do_recover_subnet::RecoverSubnetPayload, + do_remove_api_boundary_nodes::RemoveApiBoundaryNodesPayload, + do_remove_nodes_from_subnet::RemoveNodesFromSubnetPayload, + do_retire_replica_version::RetireReplicaVersionPayload, + do_revise_elected_replica_versions::ReviseElectedGuestosVersionsPayload, + do_set_firewall_config::SetFirewallConfigPayload, + do_update_api_boundary_nodes_version::UpdateApiBoundaryNodesVersionPayload, + do_update_elected_hostos_versions::UpdateElectedHostosVersionsPayload, + do_update_node_operator_config::UpdateNodeOperatorConfigPayload, + do_update_nodes_hostos_version::UpdateNodesHostosVersionPayload, + do_update_ssh_readonly_access_for_all_unassigned_nodes::UpdateSshReadOnlyAccessForAllUnassignedNodesPayload, + do_update_subnet::UpdateSubnetPayload, + do_update_unassigned_nodes_config::UpdateUnassignedNodesConfigPayload, + firewall::{AddFirewallRulesPayload, RemoveFirewallRulesPayload, UpdateFirewallRulesPayload}, + node_management::do_remove_nodes::RemoveNodesPayload, + prepare_canister_migration::PrepareCanisterMigrationPayload, + reroute_canister_ranges::RerouteCanisterRangesPayload, +}; +use serde::{Deserialize, Serialize}; use spinners::{Spinner, Spinners}; use std::{ collections::{HashMap, HashSet}, @@ -6,13 +49,17 @@ use std::{ sync::Mutex, time::Duration, }; +use strum::IntoEnumIterator; use ic_canisters::{ governance::GovernanceCanisterWrapper, management::WalletCanisterWrapper, registry::RegistryCanisterWrapper, CanisterClient, IcAgentCanisterClient, }; -use ic_nns_governance::pb::v1::ProposalInfo; -use log::{info, warn}; +use ic_nns_governance::{ + governance::{BitcoinSetConfigProposal, SubnetRentalRequest}, + pb::v1::{proposal::Action, ListProposalInfo, ProposalInfo, ProposalStatus, Topic}, +}; +use log::{error, info, warn}; use url::Url; use crate::detect_neuron::{Auth, Neuron}; @@ -150,3 +197,322 @@ pub async fn get_node_metrics_history( Ok(()) } + +pub async fn filter_proposals( + network: Network, + limit: &u32, + statuses: Vec, + topics: Vec, +) -> anyhow::Result<()> { + let nns_url = match network.get_nns_urls().first() { + Some(url) => url, + None => return Err(anyhow::anyhow!("Could not get NNS URL from network config")), + }; + let client = GovernanceCanisterWrapper::from(CanisterClient::from_anonymous(nns_url)?); + + let exclude_topic = match topics.is_empty() { + true => vec![], + false => { + let mut all_topics = Topic::iter().collect_vec(); + for topic in &topics { + all_topics.retain(|t| t != topic); + } + all_topics + } + }; + + let mut remaining = *limit; + let mut proposals: Vec = vec![]; + let mut payload = ListProposalInfo { + before_proposal: None, + exclude_topic: exclude_topic.clone().into_iter().map(|t| t.into()).collect_vec(), + include_status: statuses.clone().into_iter().map(|s| s.into()).collect_vec(), + include_all_manage_neuron_proposals: Some(true), + ..Default::default() + }; + info!( + "Querying {} proposals where status is {} and topic is {}", + limit, + match statuses.is_empty() { + true => "any".to_string(), + false => format!("{:?}", statuses), + }, + match exclude_topic.is_empty() { + true => "any".to_string(), + false => format!("not in {:?}", exclude_topic), + } + ); + loop { + let current_batch = client + .list_proposals(payload) + .await? + .into_iter() + .filter_map(|p| match p.clone().try_into() { + Ok(p) => Some(p), + Err(e) => { + error!("Error converting proposal info {:?}: {:?}", p, e); + None + } + }) + .sorted_by(|a: &Proposal, b: &Proposal| b.id.cmp(&a.id)) + .collect_vec(); + payload = ListProposalInfo { + before_proposal: current_batch.clone().last().map(|p| ProposalId { id: p.id }), + exclude_topic: exclude_topic.clone().into_iter().map(|t| t.into()).collect_vec(), + include_status: statuses.clone().into_iter().map(|s| s.into()).collect_vec(), + include_all_manage_neuron_proposals: Some(true), + ..Default::default() + }; + + if current_batch.len() > remaining as usize { + let current_batch = current_batch.into_iter().take(remaining as usize).collect_vec(); + remaining = 0; + proposals.extend(current_batch) + } else { + remaining -= current_batch.len() as u32; + proposals.extend(current_batch) + } + + info!("Remaining after iteration: {}", remaining); + + if remaining == 0 { + break; + } + + if payload.before_proposal.is_none() { + warn!( + "No more proposals available and there is {} remaining to find", + remaining + ); + break; + } + } + println!("{}", serde_json::to_string_pretty(&proposals)?); + + Ok(()) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Proposal { + id: u64, + proposer: u64, + title: String, + summary: String, + proposal_timestamp_seconds: u64, + topic: Topic, + status: ProposalStatus, + payload: String, +} + +impl TryFrom for Proposal { + fn try_from(value: ProposalInfo) -> Result { + let proposal = value.proposal.clone().unwrap(); + Ok(Self { + id: value.id.unwrap().id, + proposal_timestamp_seconds: value.proposal_timestamp_seconds, + proposer: value.proposer.unwrap().id, + status: value.status(), + summary: proposal.summary, + title: proposal.title.unwrap_or_default(), + topic: value.topic(), + payload: match proposal.action.unwrap() { + Action::ManageNeuron(a) => serde_json::to_string(&a.command)?, + Action::ManageNetworkEconomics(a) => serde_json::to_string(&a)?, + Action::Motion(a) => serde_json::to_string(&a)?, + Action::ExecuteNnsFunction(a) => { + if a.payload.is_empty() { + "".to_string() + } else { + match a.nns_function() { + ic_nns_governance::pb::v1::NnsFunction::Unspecified => serde_json::to_string(&a)?, + ic_nns_governance::pb::v1::NnsFunction::CreateSubnet => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), CreateSubnetPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::AddNodeToSubnet => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), AddNodesToSubnetPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::NnsCanisterInstall => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), AddCanisterRequest)?))? + } + ic_nns_governance::pb::v1::NnsFunction::NnsCanisterUpgrade => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), ChangeCanisterRequest))?)? + } + ic_nns_governance::pb::v1::NnsFunction::BlessReplicaVersion => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), BlessReplicaVersionPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::RecoverSubnet => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RecoverSubnetPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateConfigOfSubnet => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), UpdateSubnetPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::AssignNoid => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), AddNodeOperatorPayload)?))? + } + // Unable to resolve rustls deps when adding `ic-nns-test-utils` + ic_nns_governance::pb::v1::NnsFunction::NnsRootUpgrade => "".to_string(), + ic_nns_governance::pb::v1::NnsFunction::IcpXdrConversionRate => serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateIcpXdrConversionRatePayload)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::DeployGuestosToAllSubnetNodes => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), DeployGuestosToAllSubnetNodesPayload)?), + )? + } + // Has an empty payload + ic_nns_governance::pb::v1::NnsFunction::ClearProvisionalWhitelist => "".to_string(), + ic_nns_governance::pb::v1::NnsFunction::RemoveNodesFromSubnet => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RemoveNodesFromSubnetPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::SetAuthorizedSubnetworks => serde_json::to_string( + &(Decode!(a.payload.as_slice(), SetAuthorizedSubnetworkListArgs)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::SetFirewallConfig => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), SetFirewallConfigPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateNodeOperatorConfig => serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateNodeOperatorConfigPayload)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::StopOrStartNnsCanister => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), StopOrStartCanisterRequest)?))? + } + ic_nns_governance::pb::v1::NnsFunction::RemoveNodes => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RemoveNodesPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UninstallCode => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), CanisterIdRecord)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateNodeRewardsTable => serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateNodeRewardsTableProposalPayload)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::AddOrRemoveDataCenters => serde_json::to_string( + &(Decode!(a.payload.as_slice(), AddOrRemoveDataCentersProposalPayload)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::UpdateUnassignedNodesConfig => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateUnassignedNodesConfigPayload)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::RemoveNodeOperators => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RemoveNodeOperatorsPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::RerouteCanisterRanges => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RerouteCanisterRangesPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::AddFirewallRules => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), AddFirewallRulesPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::RemoveFirewallRules => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RemoveFirewallRulesPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateFirewallRules => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), UpdateFirewallRulesPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::PrepareCanisterMigration => serde_json::to_string( + &(Decode!(a.payload.as_slice(), PrepareCanisterMigrationPayload)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::CompleteCanisterMigration => serde_json::to_string( + &(Decode!(a.payload.as_slice(), CompleteCanisterMigrationPayload)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::AddSnsWasm => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), AddWasmRequest)?))? + } + ic_nns_governance::pb::v1::NnsFunction::ChangeSubnetMembership => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), ChangeSubnetMembershipPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateSubnetType => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), UpdateSubnetPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::ChangeSubnetTypeAssignment => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), UpdateSubnetPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateSnsWasmSnsSubnetIds => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), UpdateSnsSubnetListRequest)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateAllowedPrincipals => serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateAllowedPrincipalsRequest)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::RetireReplicaVersion => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RetireReplicaVersionPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::InsertSnsWasmUpgradePathEntries => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), InsertUpgradePathEntriesRequest)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::ReviseElectedGuestosVersions => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), ReviseElectedGuestosVersionsPayload)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::BitcoinSetConfig => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), BitcoinSetConfigProposal)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateElectedHostosVersions => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateElectedHostosVersionsPayload)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateNodesHostosVersion => serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateNodesHostosVersionPayload)?), + )?, + // Unable to resolve rustls deps when adding `ic-nns-test-utils` + ic_nns_governance::pb::v1::NnsFunction::HardResetNnsRootToVersion => "".to_string(), + ic_nns_governance::pb::v1::NnsFunction::AddApiBoundaryNodes => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), AddApiBoundaryNodesPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::RemoveApiBoundaryNodes => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), RemoveApiBoundaryNodesPayload)?))? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateApiBoundaryNodesVersion => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateApiBoundaryNodesVersionPayload)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::DeployGuestosToSomeApiBoundaryNodes => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateApiBoundaryNodesVersionPayload)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::DeployGuestosToAllUnassignedNodes => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), DeployGuestosToAllUnassignedNodesPayload)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::UpdateSshReadonlyAccessForAllUnassignedNodes => { + serde_json::to_string( + &(Decode!( + a.payload.as_slice(), + UpdateSshReadOnlyAccessForAllUnassignedNodesPayload + )?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::ReviseElectedHostosVersions => { + serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateElectedHostosVersionsPayload)?), + )? + } + ic_nns_governance::pb::v1::NnsFunction::DeployHostosToSomeNodes => serde_json::to_string( + &(Decode!(a.payload.as_slice(), UpdateNodesHostosVersionPayload)?), + )?, + ic_nns_governance::pb::v1::NnsFunction::SubnetRentalRequest => { + serde_json::to_string(&(Decode!(a.payload.as_slice(), SubnetRentalRequest)?))? + } + } + } + } + Action::ApproveGenesisKyc(a) => serde_json::to_string(&a)?, + Action::AddOrRemoveNodeProvider(a) => serde_json::to_string(&a)?, + Action::RewardNodeProvider(a) => serde_json::to_string(&a)?, + Action::SetDefaultFollowees(a) => serde_json::to_string(&a)?, + Action::RewardNodeProviders(a) => serde_json::to_string(&a)?, + Action::RegisterKnownNeuron(a) => serde_json::to_string(&a)?, + Action::SetSnsTokenSwapOpenTimeWindow(a) => serde_json::to_string(&a)?, + Action::OpenSnsTokenSwap(a) => serde_json::to_string(&a)?, + Action::CreateServiceNervousSystem(a) => serde_json::to_string(&a)?, + }, + }) + } + + type Error = anyhow::Error; +} diff --git a/rs/cli/src/main.rs b/rs/cli/src/main.rs index 24a972cc..afc89e38 100644 --- a/rs/cli/src/main.rs +++ b/rs/cli/src/main.rs @@ -3,7 +3,7 @@ use clap::{error::ErrorKind, CommandFactory, Parser}; use dialoguer::Confirm; use dotenv::dotenv; use dre::detect_neuron::Auth; -use dre::general::{get_node_metrics_history, vote_on_proposals}; +use dre::general::{filter_proposals, get_node_metrics_history, vote_on_proposals}; use dre::operations::hostos_rollout::{NodeGroupUpdate, NumberOfNodes}; use dre::{cli, ic_admin, local_unused_port, registry_dump, runner}; use ic_base_types::CanisterId; @@ -355,6 +355,9 @@ async fn async_main() -> Result<(), anyhow::Error> { println!("{}", proposals); Ok(()) }, + cli::proposals::Commands::Filter { limit, statuses, topics } => { + filter_proposals(target_network, limit, statuses.iter().map(|s| s.clone().into()).collect(), topics.iter().map(|t| t.clone().into()).collect()).await + } }, } })