From 3c6f329671d72d8a7b585d01153ee5a24c1b42ba Mon Sep 17 00:00:00 2001 From: Andre Popovitch Date: Mon, 30 Sep 2024 20:46:04 -0700 Subject: [PATCH] begin sketch --- .../src/pocket_ic_helpers.rs | 4 +- rs/sns/governance/canister/canister.rs | 13 ++++- rs/sns/governance/canister/governance.did | 7 +++ .../governance/canister/governance_test.did | 7 +++ .../ic_sns_governance/pb/v1/governance.proto | 20 ++++++++ .../src/gen/ic_sns_governance.pb.v1.rs | 43 ++++++++++++++++ rs/sns/governance/src/governance.rs | 40 ++++++++++++++- rs/sns/governance/src/proposal.rs | 2 + rs/sns/governance/src/request_impls.rs | 51 +++++++++---------- rs/sns/governance/src/sns_upgrade.rs | 43 ++++++++++++++++ 10 files changed, 198 insertions(+), 32 deletions(-) diff --git a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs index da0ec1f61b6..e300c71aaaf 100644 --- a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs +++ b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs @@ -198,10 +198,10 @@ pub fn propose_to_set_network_economics_and_wait( pub fn add_wasms_to_sns_wasm( pocket_ic: &PocketIc, - with_mainnet_ledger_wasms: bool, + with_mainnet_sns_canister_wasms: bool, ) -> Result, String> { let (root_wasm, governance_wasm, swap_wasm, index_wasm, ledger_wasm, archive_wasm) = - if with_mainnet_ledger_wasms { + if with_mainnet_sns_canister_wasms { ( ensure_sns_wasm_gzipped(build_mainnet_root_sns_wasm()), ensure_sns_wasm_gzipped(build_mainnet_governance_sns_wasm()), diff --git a/rs/sns/governance/canister/canister.rs b/rs/sns/governance/canister/canister.rs index 7376dde5bb9..c69e2b45500 100644 --- a/rs/sns/governance/canister/canister.rs +++ b/rs/sns/governance/canister/canister.rs @@ -32,8 +32,8 @@ use ic_nervous_system_runtime::DfnRuntime; use ic_nns_constants::LEDGER_CANISTER_ID as NNS_LEDGER_CANISTER_ID; #[cfg(feature = "test")] use ic_sns_governance::pb::v1::{ - AddMaturityRequest, AddMaturityResponse, GovernanceError, MintTokensRequest, - MintTokensResponse, Neuron, + AddMaturityRequest, AddMaturityResponse, AdvanceTargetVersionRequest, + AdvanceTargetVersionResponse, GovernanceError, MintTokensRequest, MintTokensResponse, Neuron, }; use ic_sns_governance::{ governance::{ @@ -524,6 +524,7 @@ fn get_running_sns_version_(_: GetRunningSnsVersionRequest) -> GetRunningSnsVers GetRunningSnsVersionResponse { deployed_version: governance().proto.deployed_version.clone(), pending_version: governance().proto.pending_version.clone(), + target_version: governance().proto.target_version.clone(), } } @@ -726,6 +727,14 @@ fn add_maturity_(request: AddMaturityRequest) -> AddMaturityResponse { governance_mut().add_maturity(request) } +#[cfg(feature = "test")] +#[export_name = "canister_update advance_target_version"] +fn advance_target_version(request: AdvanceTargetVersionRequest) { + over(candid_one, |request: AdvanceTargetVersionRequest| { + AdvanceTargetVersionResponse {} + }) +} + /// Mints tokens for testing #[cfg(feature = "test")] #[export_name = "canister_update mint_tokens"] diff --git a/rs/sns/governance/canister/governance.did b/rs/sns/governance/canister/governance.did index 6ac1061e143..f990d19118a 100644 --- a/rs/sns/governance/canister/governance.did +++ b/rs/sns/governance/canister/governance.did @@ -258,12 +258,17 @@ type GetProposalResponse = record { type GetRunningSnsVersionResponse = record { deployed_version : opt Version; pending_version : opt UpgradeInProgress; + target_version : opt Version; }; type GetSnsInitializationParametersResponse = record { sns_initialization_parameters : text; }; +type CachedUpgradeSteps = record { + upgrade_steps : vec Version; +}; + type Governance = record { root_canister_id : opt principal; id_to_nervous_system_functions : vec record { nat64; NervousSystemFunction }; @@ -273,6 +278,8 @@ type Governance = record { parameters : opt NervousSystemParameters; is_finalizing_disburse_maturity : opt bool; deployed_version : opt Version; + target_version : opt Version; + cached_upgrade_steps : opt CachedUpgradeSteps; sns_initialization_parameters : text; latest_reward_event : opt RewardEvent; pending_version : opt UpgradeInProgress; diff --git a/rs/sns/governance/canister/governance_test.did b/rs/sns/governance/canister/governance_test.did index d005ebf1ea8..d8c387a5247 100644 --- a/rs/sns/governance/canister/governance_test.did +++ b/rs/sns/governance/canister/governance_test.did @@ -267,12 +267,17 @@ type GetProposalResponse = record { type GetRunningSnsVersionResponse = record { deployed_version : opt Version; pending_version : opt UpgradeInProgress; + target_version : opt Version; }; type GetSnsInitializationParametersResponse = record { sns_initialization_parameters : text; }; +type CachedUpgradeSteps = record { + upgrade_steps : vec Version; +}; + type Governance = record { root_canister_id : opt principal; id_to_nervous_system_functions : vec record { nat64; NervousSystemFunction }; @@ -282,6 +287,8 @@ type Governance = record { parameters : opt NervousSystemParameters; is_finalizing_disburse_maturity : opt bool; deployed_version : opt Version; + target_version : opt Version; + cached_upgrade_steps : opt CachedUpgradeSteps; sns_initialization_parameters : text; latest_reward_event : opt RewardEvent; pending_version : opt UpgradeInProgress; diff --git a/rs/sns/governance/proto/ic_sns_governance/pb/v1/governance.proto b/rs/sns/governance/proto/ic_sns_governance/pb/v1/governance.proto index 8fc4f625702..5c38caa798f 100644 --- a/rs/sns/governance/proto/ic_sns_governance/pb/v1/governance.proto +++ b/rs/sns/governance/proto/ic_sns_governance/pb/v1/governance.proto @@ -1187,6 +1187,7 @@ message RewardEvent { optional uint64 total_available_e8s_equivalent = 8; } + // The representation of the whole governance system, containing all // information about the governance system that must be kept // across upgrades of the governance system, i.e. kept in stable memory. @@ -1449,6 +1450,15 @@ message Governance { reserved "migrated_root_wasm_memory_limit"; reserved 27; + + Governance.Version target_version = 28; + + message CachedUpgradeSteps { + repeated Governance.Version upgrade_steps = 1; + optional uint64 requested_update_at_timestamp_seconds = 2; + optional uint64 received_update_at_timestamp_seconds = 3; + } + CachedUpgradeSteps cached_upgrade_steps = 29; } // Request message for 'get_metadata'. @@ -1480,6 +1490,8 @@ message GetRunningSnsVersionResponse { Governance.Version deployed_version = 1; // The upgrade in progress, if any. Governance.UpgradeInProgress pending_version = 2; + // The target version that the SNS is in the process of upgrading to. + Governance.Version target_version = 3; } // Request to fail an upgrade proposal that is Adopted but not Executed or @@ -2138,3 +2150,11 @@ message Account { // subaccount (all bytes set to 0) is used. optional Subaccount subaccount = 2; } + +message AdvanceTargetVersionRequest { + +} + +message AdvanceTargetVersionResponse { + +} \ No newline at end of file diff --git a/rs/sns/governance/src/gen/ic_sns_governance.pb.v1.rs b/rs/sns/governance/src/gen/ic_sns_governance.pb.v1.rs index 3a3b8605f9f..6888ad48189 100644 --- a/rs/sns/governance/src/gen/ic_sns_governance.pb.v1.rs +++ b/rs/sns/governance/src/gen/ic_sns_governance.pb.v1.rs @@ -1644,6 +1644,10 @@ pub struct Governance { pub is_finalizing_disburse_maturity: ::core::option::Option, #[prost(message, optional, tag = "26")] pub maturity_modulation: ::core::option::Option, + #[prost(message, optional, tag = "28")] + pub target_version: ::core::option::Option, + #[prost(message, optional, tag = "29")] + pub cached_upgrade_steps: ::core::option::Option, } /// Nested message and enum types in `Governance`. pub mod governance { @@ -1904,6 +1908,22 @@ pub mod governance { #[prost(uint64, optional, tag = "2")] pub updated_at_timestamp_seconds: ::core::option::Option, } + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, + )] + pub struct CachedUpgradeSteps { + #[prost(message, repeated, tag = "1")] + pub upgrade_steps: ::prost::alloc::vec::Vec, + #[prost(uint64, optional, tag = "2")] + pub requested_update_at_timestamp_seconds: ::core::option::Option, + #[prost(uint64, optional, tag = "3")] + pub received_update_at_timestamp_seconds: ::core::option::Option, + } #[derive( candid::CandidType, candid::Deserialize, @@ -2034,6 +2054,9 @@ pub struct GetRunningSnsVersionResponse { /// The upgrade in progress, if any. #[prost(message, optional, tag = "2")] pub pending_version: ::core::option::Option, + /// The target version that the SNS is in the process of upgrading to. + #[prost(message, optional, tag = "3")] + pub target_version: ::core::option::Option, } /// Request to fail an upgrade proposal that is Adopted but not Executed or /// Failed if it is past the time when it should have been marked as failed. @@ -3330,6 +3353,26 @@ pub struct Account { #[prost(message, optional, tag = "2")] pub subaccount: ::core::option::Option, } +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Message, +)] +pub struct AdvanceTargetVersionRequest {} +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Message, +)] +pub struct AdvanceTargetVersionResponse {} /// The different types of neuron permissions, i.e., privileges to modify a neuron, /// that principals can have. #[derive( diff --git a/rs/sns/governance/src/governance.rs b/rs/sns/governance/src/governance.rs index 2f44c7ef0e7..2f61407290e 100644 --- a/rs/sns/governance/src/governance.rs +++ b/rs/sns/governance/src/governance.rs @@ -20,7 +20,7 @@ use crate::{ governance::{ self, neuron_in_flight_command::{self, Command as InFlightCommand}, - MaturityModulation, NeuronInFlightCommand, SnsMetadata, UpgradeInProgress, Version, + MaturityModulation, NeuronInFlightCommand, SnsMetadata, UpgradeInProgress, Version, CachedUpgradeSteps }, governance_error::ErrorType, manage_neuron::{ @@ -4632,6 +4632,44 @@ impl Governance { self.maybe_move_staked_maturity(); self.maybe_gc(); + + self.maybe_update_cached_upgrade_steps().await; + } + + async fn maybe_update_cached_upgrade_steps(&mut self) { + let update_interval = 5 * 60; // 5 minutes // TODO: move to a constant + let should_update = match self.proto.cached_upgrade_steps.clone() { + Some(cached_upgrade_steps) => { + let requested_update_at_timestamp_seconds = cached_upgrade_steps.requested_update_at_timestamp_seconds.unwrap_or(0); + let received_update_at_timestamp_seconds = cached_upgrade_steps.received_update_at_timestamp_seconds.unwrap_or(0); + let last_update = requested_update_at_timestamp_seconds.max(received_update_at_timestamp_seconds); + self.env.now() - last_update > update_interval + } + None => { + true + } + }; + + if should_update { + let current_version = self.proto.deployed_version_or_panic(); + let requested_update_at_timestamp_seconds = self.env.now(); + self.proto.cached_upgrade_steps = Some(CachedUpgradeSteps { + requested_update_at_timestamp_seconds: Some(requested_update_at_timestamp_seconds), + ..self.proto.cached_upgrade_steps.clone().unwrap_or_default() + }); + match crate::sns_upgrade::get_upgrade_path(&*self.env, current_version, self.env.canister_id().get()).await { + Ok(upgrade_path) => { + self.proto.cached_upgrade_steps = Some(CachedUpgradeSteps { + upgrade_steps: upgrade_path, + requested_update_at_timestamp_seconds: Some(requested_update_at_timestamp_seconds), + received_update_at_timestamp_seconds: Some(self.env.now()), + }); + } + Err(err) => { + log!(ERROR, "Failed to get upgrade path: {}", err); + } + } + } } fn should_update_maturity_modulation(&self) -> bool { diff --git a/rs/sns/governance/src/proposal.rs b/rs/sns/governance/src/proposal.rs index 36f9bce70dd..f78d91c4d95 100644 --- a/rs/sns/governance/src/proposal.rs +++ b/rs/sns/governance/src/proposal.rs @@ -2489,6 +2489,8 @@ mod tests { pending_version: None, is_finalizing_disburse_maturity: None, maturity_modulation: None, + target_version: None, + cached_upgrade_steps: None, } } diff --git a/rs/sns/governance/src/request_impls.rs b/rs/sns/governance/src/request_impls.rs index db59152ce16..abe2fcc2f60 100644 --- a/rs/sns/governance/src/request_impls.rs +++ b/rs/sns/governance/src/request_impls.rs @@ -1,82 +1,79 @@ use ic_nervous_system_clients::Request; -use crate::pb::v1::{ - ClaimSwapNeuronsRequest, ClaimSwapNeuronsResponse, FailStuckUpgradeInProgressRequest, - FailStuckUpgradeInProgressResponse, GetMaturityModulationRequest, - GetMaturityModulationResponse, GetMetadataRequest, GetMetadataResponse, GetMode, - GetModeResponse, GetNeuronResponse, GetProposalResponse, GetRunningSnsVersionResponse, - GetSnsInitializationParametersRequest, GetSnsInitializationParametersResponse, - ListNeuronsResponse, ListProposalsResponse, ManageNeuronResponse, -}; - -impl Request for ClaimSwapNeuronsRequest { - type Response = ClaimSwapNeuronsResponse; +impl Request for crate::pb::v1::ClaimSwapNeuronsRequest { + type Response = crate::pb::v1::ClaimSwapNeuronsResponse; const METHOD: &'static str = "claim_swap_neurons"; const UPDATE: bool = true; } -impl Request for FailStuckUpgradeInProgressRequest { - type Response = FailStuckUpgradeInProgressResponse; +impl Request for crate::pb::v1::FailStuckUpgradeInProgressRequest { + type Response = crate::pb::v1::FailStuckUpgradeInProgressResponse; const METHOD: &'static str = "fail_stuck_upgrade_in_progress"; const UPDATE: bool = true; } -impl Request for GetMaturityModulationRequest { - type Response = GetMaturityModulationResponse; +impl Request for crate::pb::v1::GetMaturityModulationRequest { + type Response = crate::pb::v1::GetMaturityModulationResponse; const METHOD: &'static str = "get_maturity_modulation"; const UPDATE: bool = false; } -impl Request for GetMetadataRequest { - type Response = GetMetadataResponse; +impl Request for crate::pb::v1::GetMetadataRequest { + type Response = crate::pb::v1::GetMetadataResponse; const METHOD: &'static str = "get_metadata"; const UPDATE: bool = false; } -impl Request for GetSnsInitializationParametersRequest { - type Response = GetSnsInitializationParametersResponse; +impl Request for crate::pb::v1::GetSnsInitializationParametersRequest { + type Response = crate::pb::v1::GetSnsInitializationParametersResponse; const METHOD: &'static str = "get_sns_initialization_parameters"; const UPDATE: bool = false; } -impl Request for GetMode { - type Response = GetModeResponse; +impl Request for crate::pb::v1::GetMode { + type Response = crate::pb::v1::GetModeResponse; const METHOD: &'static str = "get_mode"; const UPDATE: bool = false; } impl Request for crate::pb::v1::GetNeuron { - type Response = GetNeuronResponse; + type Response = crate::pb::v1::GetNeuronResponse; const METHOD: &'static str = "get_neuron"; const UPDATE: bool = false; } impl Request for crate::pb::v1::GetProposal { - type Response = GetProposalResponse; + type Response = crate::pb::v1::GetProposalResponse; const METHOD: &'static str = "get_proposal"; const UPDATE: bool = false; } impl Request for crate::pb::v1::ListNeurons { - type Response = ListNeuronsResponse; + type Response = crate::pb::v1::ListNeuronsResponse; const METHOD: &'static str = "list_neurons"; const UPDATE: bool = false; } impl Request for crate::pb::v1::ListProposals { - type Response = ListProposalsResponse; + type Response = crate::pb::v1::ListProposalsResponse; const METHOD: &'static str = "list_proposals"; const UPDATE: bool = false; } impl Request for crate::pb::v1::ManageNeuron { - type Response = ManageNeuronResponse; + type Response = crate::pb::v1::ManageNeuronResponse; const METHOD: &'static str = "manage_neuron"; const UPDATE: bool = true; } impl Request for crate::pb::v1::GetRunningSnsVersionRequest { - type Response = GetRunningSnsVersionResponse; + type Response = crate::pb::v1::GetRunningSnsVersionResponse; const METHOD: &'static str = "get_running_sns_version"; + const UPDATE: bool = false; +} + +impl Request for crate::pb::v1::AdvanceTargetVersionRequest { + type Response = crate::pb::v1::AdvanceTargetVersionResponse; + const METHOD: &'static str = "advance_target_version"; const UPDATE: bool = true; } diff --git a/rs/sns/governance/src/sns_upgrade.rs b/rs/sns/governance/src/sns_upgrade.rs index 9a1b336f7f9..1badc5870e6 100644 --- a/rs/sns/governance/src/sns_upgrade.rs +++ b/rs/sns/governance/src/sns_upgrade.rs @@ -293,6 +293,31 @@ async fn get_next_version(env: &dyn Environment, current_version: &Version) -> O response.next_version.map(|v| v.into()) } +pub(crate) async fn get_upgrade_path(env: &dyn Environment, current_version: Version, sns_governance_canister_id: PrincipalId) -> Result, String> { + let request = ListUpgradeStepsRequest { + starting_at: Some(current_version.into()), + sns_governance_canister_id: Some(sns_governance_canister_id), + limit: 0, + }; + let arg = Encode!(&request).map_err(|e| format!("Could not encode ListUpgradeStepsRequest: {:?}", e))?; + + let response = env + .call_canister(SNS_WASM_CANISTER_ID, "list_upgrade_steps", arg) + .await + .map_err(|e| format!("Request failed for get_next_sns_version: {:?}", e))?; + + let response = Decode!(&response, ListUpgradeStepsResponse) + .map_err(|e| format!("Could not decode response to get_next_sns_version: {:?}", e))?; + let response_str = format!("{:?}", response); + + response.steps.into_iter().map(|list_upgrade_step| match list_upgrade_step { + ListUpgradeStep { + version: Some(version), + } => Ok(version.into()), + _ => Err(format!("list_upgrade_steps response had invalid fields: {}", response_str)) + }).collect() +} + /// Returns all SNS canisters known by the Root canister. pub(crate) async fn get_all_sns_canisters( env: &dyn Environment, @@ -602,3 +627,21 @@ pub struct GetProposalIdThatAddedWasmResponse { #[prost(uint64, optional, tag = "1")] pub proposal_id: ::core::option::Option, } + +#[derive(Clone, PartialEq, candid::CandidType, candid::Deserialize)] +pub(crate) struct ListUpgradeStepsRequest { + /// If provided, limit response to only include entries for this version and later + pub(crate) starting_at: ::core::option::Option, + /// If provided, give responses that this canister would get back + pub(crate) sns_governance_canister_id: ::core::option::Option<::ic_base_types::PrincipalId>, + /// Limit to number of entries (for paging) + pub(crate) limit: u32, +} +#[derive(candid::CandidType, candid::Deserialize, Debug)] +pub(crate) struct ListUpgradeStepsResponse { + pub(crate) steps: ::prost::alloc::vec::Vec, +} +#[derive(candid::CandidType, candid::Deserialize, Debug)] +pub(crate) struct ListUpgradeStep { + pub(crate) version: ::core::option::Option, +}