diff --git a/rs/sns/governance/canister/governance.did b/rs/sns/governance/canister/governance.did index dcadb5f603c..59229ef302a 100644 --- a/rs/sns/governance/canister/governance.did +++ b/rs/sns/governance/canister/governance.did @@ -290,8 +290,9 @@ type Governance = record { sns_metadata : opt ManageSnsMetadata; neurons : vec record { text; Neuron }; genesis_timestamp_seconds : nat64; - target_version: opt Version; + target_version : opt Version; timers : opt Timers; + upgrade_journal : opt UpgradeJournal; }; type Timers = record { @@ -719,12 +720,56 @@ type WaitForQuietState = record { current_deadline_timestamp_seconds : nat64; }; +type UpgradeJournalEntry = record { + event : opt variant { + UpgradeStepsRefreshed : UpgradeStepsRefreshed; + TargetVersionSet : TargetVersionSet; + TargetVersionReset : TargetVersionSet; + UpgradeStarted : UpgradeStarted; + UpgradeOutcome : UpgradeOutcome; + }; + timestamp_seconds : opt nat64; +}; + +type UpgradeStepsRefreshed = record { + upgrade_steps : opt Versions; +}; + +type TargetVersionSet = record { + new_target_version : opt Version; + old_target_version : opt Version; +}; + +type UpgradeStarted = record { + current_version : opt Version; + expected_version : opt Version; + reason : opt variant { + UpgradeSnsToNextVersionProposal : ProposalId; + BehindTargetVersion : record {}; + } +}; + +type UpgradeOutcome = record { + human_readable : opt text; + status : opt variant { + Success : record {}; + Timeout : record {}; + InvalidState : record { version : opt Version }; + ExternalFailure : record {}; + }; +}; + +type UpgradeJournal = record { + entries : vec UpgradeJournalEntry; +}; + type GetUpgradeJournalRequest = record {}; type GetUpgradeJournalResponse = record { upgrade_steps : opt Versions; response_timestamp_seconds : opt nat64; target_version : opt Version; + upgrade_journal : opt UpgradeJournal; }; service : (Governance) -> { @@ -740,13 +785,9 @@ service : (Governance) -> { get_proposal : (GetProposal) -> (GetProposalResponse) query; get_root_canister_status : (null) -> (CanisterStatusResultV2); get_running_sns_version : (record {}) -> (GetRunningSnsVersionResponse) query; - get_sns_initialization_parameters : (record {}) -> ( - GetSnsInitializationParametersResponse, - ) query; + get_sns_initialization_parameters : (record {}) -> (GetSnsInitializationParametersResponse) query; get_upgrade_journal : (GetUpgradeJournalRequest) -> (GetUpgradeJournalResponse) query; - list_nervous_system_functions : () -> ( - ListNervousSystemFunctionsResponse, - ) query; + list_nervous_system_functions : () -> (ListNervousSystemFunctionsResponse) query; list_neurons : (ListNeurons) -> (ListNeuronsResponse) query; list_proposals : (ListProposals) -> (ListProposalsResponse) query; manage_neuron : (ManageNeuron) -> (ManageNeuronResponse); diff --git a/rs/sns/governance/canister/governance_test.did b/rs/sns/governance/canister/governance_test.did index 965b384e4fc..62b2920f222 100644 --- a/rs/sns/governance/canister/governance_test.did +++ b/rs/sns/governance/canister/governance_test.did @@ -301,6 +301,7 @@ type Governance = record { genesis_timestamp_seconds : nat64; target_version: opt Version; timers : opt Timers; + upgrade_journal : opt UpgradeJournal; }; type Timers = record { @@ -733,12 +734,56 @@ type WaitForQuietState = record { current_deadline_timestamp_seconds : nat64; }; +type UpgradeJournalEntry = record { + event : opt variant { + UpgradeStepsRefreshed : UpgradeStepsRefreshed; + TargetVersionSet : TargetVersionSet; + TargetVersionReset : TargetVersionSet; + UpgradeStarted : UpgradeStarted; + UpgradeOutcome : UpgradeOutcome; + }; + timestamp_seconds : opt nat64; +}; + +type UpgradeStepsRefreshed = record { + upgrade_steps : opt Versions; +}; + +type TargetVersionSet = record { + new_target_version : opt Version; + old_target_version : opt Version; +}; + +type UpgradeStarted = record { + current_version : opt Version; + expected_version : opt Version; + reason : opt variant { + UpgradeSnsToNextVersionProposal : ProposalId; + BehindTargetVersion : record {}; + } +}; + +type UpgradeOutcome = record { + human_readable : opt text; + status : opt variant { + Success : record {}; + Timeout : record {}; + InvalidState : record { version : opt Version }; + ExternalFailure : record {}; + }; +}; + +type UpgradeJournal = record { + entries : vec UpgradeJournalEntry; +}; + type GetUpgradeJournalRequest = record {}; type GetUpgradeJournalResponse = record { upgrade_steps : opt Versions; response_timestamp_seconds : opt nat64; target_version : opt Version; + upgrade_journal : opt UpgradeJournal; }; type AdvanceTargetVersionRequest = record { target_version : opt Version; }; 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 8d7544919e8..e784aec5db7 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 @@ -1473,6 +1473,21 @@ message Governance { // Information about the timers that perform periodic tasks of this Governance canister. optional ic_nervous_system.pb.v1.Timers timers = 31; + + UpgradeJournal upgrade_journal = 32; +} + +message Timers { + optional uint64 last_reset_timestamp_seconds = 1; + optional uint64 last_spawned_timestamp_seconds = 2; +} + +message ResetTimersRequest {} +message ResetTimersResponse {} + +message GetTimersRequest {} +message GetTimersResponse { + optional Timers timers = 1; } // Request message for 'get_metadata'. @@ -2140,6 +2155,58 @@ message AdvanceTargetVersionRequest { // The response to a request to advance the target version of the SNS. message AdvanceTargetVersionResponse {} +// Represents a single entry in the upgrade journal. +message UpgradeJournalEntry { + oneof event { + UpgradeStepsRefreshed upgrade_steps_refreshed = 1; + TargetVersionSet target_version_set = 2; + TargetVersionSet target_version_reset = 3; + UpgradeStarted upgrade_started = 4; + UpgradeOutcome upgrade_outcome = 5; + } + optional uint64 timestamp_seconds = 6; + + message UpgradeStepsRefreshed { + optional Governance.Versions upgrade_steps = 2; + } + + message TargetVersionSet { + optional Governance.Version old_target_version = 1; + optional Governance.Version new_target_version = 2; + } + + message UpgradeStarted { + optional Governance.Version current_version = 1; + optional Governance.Version expected_version = 2; + oneof reason { + ProposalId upgrade_sns_to_next_version_proposal = 3; + Empty behind_target_version = 4; + } + } + + message UpgradeOutcome { + optional string human_readable = 1; + + oneof status { + Empty success = 2; + Empty timeout = 3; + // The SNS ended up being upgraded to a version that was not the expected one. + InvalidState invalid_state = 4; + Empty external_failure = 5; + } + + message InvalidState { + Governance.Version version = 1; + } + } +} + +// Needed to cause prost to generate a type isomorphic to Option>. +message UpgradeJournal { + // The entries in the upgrade journal. + repeated UpgradeJournalEntry entries = 1; +} + // The upgrade journal contains all the information neede to audit previous SNS upgrades and understand its current state. // It is being implemented as part of the "effortless SNS upgrade" feature. message GetUpgradeJournalRequest {} @@ -2151,6 +2218,8 @@ message GetUpgradeJournalResponse { // Currently, this field is always None, but in the "effortless SNS upgrade" // feature, it reflect the version of the SNS that the community has decided to upgrade to. Governance.Version target_version = 3; + + UpgradeJournal upgrade_journal = 4; } // A request to mint tokens for a particular principal. The associated endpoint 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 c926e3465df..8636368e499 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 @@ -1650,6 +1650,8 @@ pub struct Governance { /// Information about the timers that perform periodic tasks of this Governance canister. #[prost(message, optional, tag = "31")] pub timers: ::core::option::Option<::ic_nervous_system_proto::pb::v1::Timers>, + #[prost(message, optional, tag = "32")] + pub upgrade_journal: ::core::option::Option, } /// Nested message and enum types in `Governance`. pub mod governance { @@ -1996,6 +1998,64 @@ pub mod governance { } } } +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Message, +)] +pub struct Timers { + #[prost(uint64, optional, tag = "1")] + pub last_reset_timestamp_seconds: ::core::option::Option, + #[prost(uint64, optional, tag = "2")] + pub last_spawned_timestamp_seconds: ::core::option::Option, +} +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Message, +)] +pub struct ResetTimersRequest {} +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Message, +)] +pub struct ResetTimersResponse {} +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Message, +)] +pub struct GetTimersRequest {} +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Message, +)] +pub struct GetTimersResponse { + #[prost(message, optional, tag = "1")] + pub timers: ::core::option::Option, +} /// Request message for 'get_metadata'. #[derive( candid::CandidType, @@ -3334,6 +3394,166 @@ pub struct AdvanceTargetVersionRequest { ::prost::Message, )] pub struct AdvanceTargetVersionResponse {} +/// Represents a single entry in the upgrade journal. +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, +)] +pub struct UpgradeJournalEntry { + #[prost(uint64, optional, tag = "6")] + pub timestamp_seconds: ::core::option::Option, + #[prost(oneof = "upgrade_journal_entry::Event", tags = "1, 2, 3, 4, 5")] + pub event: ::core::option::Option, +} +/// Nested message and enum types in `UpgradeJournalEntry`. +pub mod upgrade_journal_entry { + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, + )] + pub struct UpgradeStepsRefreshed { + #[prost(message, optional, tag = "2")] + pub upgrade_steps: ::core::option::Option, + } + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, + )] + pub struct TargetVersionSet { + #[prost(message, optional, tag = "1")] + pub old_target_version: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub new_target_version: ::core::option::Option, + } + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, + )] + pub struct UpgradeStarted { + #[prost(message, optional, tag = "1")] + pub current_version: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub expected_version: ::core::option::Option, + #[prost(oneof = "upgrade_started::Reason", tags = "3, 4")] + pub reason: ::core::option::Option, + } + /// Nested message and enum types in `UpgradeStarted`. + pub mod upgrade_started { + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + Copy, + PartialEq, + ::prost::Oneof, + )] + pub enum Reason { + #[prost(message, tag = "3")] + UpgradeSnsToNextVersionProposal(super::super::ProposalId), + #[prost(message, tag = "4")] + BehindTargetVersion(super::super::Empty), + } + } + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, + )] + pub struct UpgradeOutcome { + #[prost(string, optional, tag = "1")] + pub human_readable: ::core::option::Option<::prost::alloc::string::String>, + #[prost(oneof = "upgrade_outcome::Status", tags = "2, 3, 4, 5")] + pub status: ::core::option::Option, + } + /// Nested message and enum types in `UpgradeOutcome`. + pub mod upgrade_outcome { + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, + )] + pub struct InvalidState { + #[prost(message, optional, tag = "1")] + pub version: ::core::option::Option, + } + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Oneof, + )] + pub enum Status { + #[prost(message, tag = "2")] + Success(super::super::Empty), + #[prost(message, tag = "3")] + Timeout(super::super::Empty), + /// The SNS ended up being upgraded to a version that was not the expected one. + #[prost(message, tag = "4")] + InvalidState(InvalidState), + #[prost(message, tag = "5")] + ExternalFailure(super::super::Empty), + } + } + #[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Oneof, + )] + pub enum Event { + #[prost(message, tag = "1")] + UpgradeStepsRefreshed(UpgradeStepsRefreshed), + #[prost(message, tag = "2")] + TargetVersionSet(TargetVersionSet), + #[prost(message, tag = "3")] + TargetVersionReset(TargetVersionSet), + #[prost(message, tag = "4")] + UpgradeStarted(UpgradeStarted), + #[prost(message, tag = "5")] + UpgradeOutcome(UpgradeOutcome), + } +} +/// Needed to cause prost to generate a type isomorphic to Option>. +#[derive( + candid::CandidType, + candid::Deserialize, + comparable::Comparable, + Clone, + PartialEq, + ::prost::Message, +)] +pub struct UpgradeJournal { + /// The entries in the upgrade journal. + #[prost(message, repeated, tag = "1")] + pub entries: ::prost::alloc::vec::Vec, +} /// The upgrade journal contains all the information neede to audit previous SNS upgrades and understand its current state. /// It is being implemented as part of the "effortless SNS upgrade" feature. #[derive( @@ -3364,6 +3584,8 @@ pub struct GetUpgradeJournalResponse { /// feature, it reflect the version of the SNS that the community has decided to upgrade to. #[prost(message, optional, tag = "3")] pub target_version: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub upgrade_journal: ::core::option::Option, } /// A request to mint tokens for a particular principal. The associated endpoint /// is only available on SNS governance, and only then when SNS governance is diff --git a/rs/sns/governance/src/governance.rs b/rs/sns/governance/src/governance.rs index 25d8128c8ad..f80fa4229ac 100644 --- a/rs/sns/governance/src/governance.rs +++ b/rs/sns/governance/src/governance.rs @@ -37,24 +37,25 @@ use crate::{ proposal::Action, proposal_data::ActionAuxiliary as ActionAuxiliaryPb, transfer_sns_treasury_funds::TransferFrom, - Account as AccountProto, AddMaturityRequest, AddMaturityResponse, Ballot, - ClaimSwapNeuronsError, ClaimSwapNeuronsRequest, ClaimSwapNeuronsResponse, - ClaimedSwapNeuronStatus, DefaultFollowees, DeregisterDappCanisters, - DisburseMaturityInProgress, Empty, ExecuteGenericNervousSystemFunction, - FailStuckUpgradeInProgressRequest, FailStuckUpgradeInProgressResponse, - GetMaturityModulationRequest, GetMaturityModulationResponse, GetMetadataRequest, - GetMetadataResponse, GetMode, GetModeResponse, GetNeuron, GetNeuronResponse, - GetProposal, GetProposalResponse, GetSnsInitializationParametersRequest, - GetSnsInitializationParametersResponse, GetUpgradeJournalResponse, - Governance as GovernanceProto, GovernanceError, ListNervousSystemFunctionsResponse, - ListNeurons, ListNeuronsResponse, ListProposals, ListProposalsResponse, - ManageDappCanisterSettings, ManageLedgerParameters, ManageNeuron, ManageNeuronResponse, - ManageSnsMetadata, MintSnsTokens, MintTokensRequest, MintTokensResponse, - NervousSystemFunction, NervousSystemParameters, Neuron, NeuronId, NeuronPermission, - NeuronPermissionList, NeuronPermissionType, Proposal, ProposalData, - ProposalDecisionStatus, ProposalId, ProposalRewardStatus, RegisterDappCanisters, - RewardEvent, Tally, TransferSnsTreasuryFunds, UpgradeSnsControlledCanister, - UpgradeSnsToNextVersion, Vote, WaitForQuietState, + upgrade_journal_entry, Account as AccountProto, AddMaturityRequest, + AddMaturityResponse, Ballot, ClaimSwapNeuronsError, ClaimSwapNeuronsRequest, + ClaimSwapNeuronsResponse, ClaimedSwapNeuronStatus, DefaultFollowees, + DeregisterDappCanisters, DisburseMaturityInProgress, Empty, + ExecuteGenericNervousSystemFunction, FailStuckUpgradeInProgressRequest, + FailStuckUpgradeInProgressResponse, GetMaturityModulationRequest, + GetMaturityModulationResponse, GetMetadataRequest, GetMetadataResponse, GetMode, + GetModeResponse, GetNeuron, GetNeuronResponse, GetProposal, GetProposalResponse, + GetSnsInitializationParametersRequest, GetSnsInitializationParametersResponse, + GetUpgradeJournalResponse, Governance as GovernanceProto, GovernanceError, + ListNervousSystemFunctionsResponse, ListNeurons, ListNeuronsResponse, ListProposals, + ListProposalsResponse, ManageDappCanisterSettings, ManageLedgerParameters, + ManageNeuron, ManageNeuronResponse, ManageSnsMetadata, MintSnsTokens, + MintTokensRequest, MintTokensResponse, NervousSystemFunction, NervousSystemParameters, + Neuron, NeuronId, NeuronPermission, NeuronPermissionList, NeuronPermissionType, + Proposal, ProposalData, ProposalDecisionStatus, ProposalId, ProposalRewardStatus, + RegisterDappCanisters, RewardEvent, Tally, TransferSnsTreasuryFunds, UpgradeJournal, + UpgradeJournalEntry, UpgradeSnsControlledCanister, UpgradeSnsToNextVersion, Vote, + WaitForQuietState, }, }, proposal::{ @@ -4710,14 +4711,14 @@ impl Governance { } pub fn temporarily_lock_refresh_cached_upgrade_steps(&mut self) { - if let Some(ref mut cached_upgrade_steps) = self.proto.cached_upgrade_steps { - cached_upgrade_steps.requested_timestamp_seconds = Some(self.env.now()); - } else { - self.proto.cached_upgrade_steps = Some(CachedUpgradeSteps { - requested_timestamp_seconds: Some(self.env.now()), - ..Default::default() - }); - } + let upgrade_steps = + self.proto + .cached_upgrade_steps + .get_or_insert_with(|| CachedUpgradeSteps { + requested_timestamp_seconds: Some(self.env.now()), + ..Default::default() + }); + upgrade_steps.requested_timestamp_seconds = Some(self.env.now()); } pub fn should_refresh_cached_upgrade_steps(&mut self) -> bool { @@ -4768,21 +4769,40 @@ impl Governance { versions: upgrade_steps, }; - // The following code must remain after the async call. - if let Some(ref mut cached_upgrade_steps) = self.proto.cached_upgrade_steps { - cached_upgrade_steps.response_timestamp_seconds = Some(self.env.now()); - cached_upgrade_steps.upgrade_steps = Some(upgrade_steps); + // Ensure `cached_upgrade_steps` is initialized + let cached_upgrade_steps = self + .proto + .cached_upgrade_steps + .get_or_insert_with(Default::default); + + // Update `response_timestamp_seconds` + cached_upgrade_steps.response_timestamp_seconds = Some(self.env.now()); + + // Refresh the upgrade steps if they have changed + if cached_upgrade_steps.upgrade_steps != Some(upgrade_steps.clone()) { + cached_upgrade_steps.upgrade_steps = Some(upgrade_steps.clone()); + self.push_to_upgrade_journal(upgrade_journal_entry::Event::UpgradeStepsRefreshed( + upgrade_journal_entry::UpgradeStepsRefreshed { + upgrade_steps: Some(upgrade_steps), + }, + )); } - // It's unlikely that cached_upgrade_steps is None, the caller is - // supposed to set the lock (by setting request_timestamp_seconds) before this function is called, - // and that requires cached_upgrade_steps != None. - // However, we handle it just in case. - else { - self.proto.cached_upgrade_steps = Some(CachedUpgradeSteps { - response_timestamp_seconds: Some(self.env.now()), - upgrade_steps: Some(upgrade_steps), - ..Default::default() - }); + } + + pub fn push_to_upgrade_journal(&mut self, event: upgrade_journal_entry::Event) { + let upgrade_journal_entry = UpgradeJournalEntry { + event: Some(event), + timestamp_seconds: Some(self.env.now()), + }; + match self.proto.upgrade_journal { + None => { + self.proto.upgrade_journal = Some(UpgradeJournal { + entries: vec![upgrade_journal_entry], + }); + } + Some(ref mut journal) => { + journal.entries.push(upgrade_journal_entry); + } } } @@ -4793,11 +4813,15 @@ impl Governance { upgrade_steps: cached_upgrade_steps.upgrade_steps, response_timestamp_seconds: cached_upgrade_steps.response_timestamp_seconds, target_version: self.proto.target_version.clone(), + // TODO(NNS1-3416): Bound the size of the response. + upgrade_journal: self.proto.upgrade_journal.clone(), }, None => GetUpgradeJournalResponse { upgrade_steps: None, response_timestamp_seconds: None, target_version: None, + // TODO(NNS1-3416): Bound the size of the response. + upgrade_journal: self.proto.upgrade_journal.clone(), }, } } diff --git a/rs/sns/governance/src/proposal.rs b/rs/sns/governance/src/proposal.rs index c74564d52f3..347f38c3d94 100644 --- a/rs/sns/governance/src/proposal.rs +++ b/rs/sns/governance/src/proposal.rs @@ -2490,6 +2490,7 @@ mod tests { cached_upgrade_steps: None, target_version: None, timers: None, + upgrade_journal: None, } } diff --git a/rs/sns/governance/src/sns_upgrade.rs b/rs/sns/governance/src/sns_upgrade.rs index 4b9d2b17892..82ea9695d7a 100644 --- a/rs/sns/governance/src/sns_upgrade.rs +++ b/rs/sns/governance/src/sns_upgrade.rs @@ -653,7 +653,7 @@ pub struct ListUpgradeStepsRequest { pub struct ListUpgradeStepsResponse { pub steps: ::prost::alloc::vec::Vec, } -#[derive(candid::CandidType, candid::Deserialize, Debug)] +#[derive(candid::CandidType, candid::Deserialize, Debug, Clone)] pub struct ListUpgradeStep { pub version: ::core::option::Option, } diff --git a/rs/sns/governance/tests/governance.rs b/rs/sns/governance/tests/governance.rs index 5395e371a73..14ebb34a9e3 100644 --- a/rs/sns/governance/tests/governance.rs +++ b/rs/sns/governance/tests/governance.rs @@ -26,7 +26,7 @@ use ic_sns_governance::{ NeuronRecipe, NeuronRecipes, }, claim_swap_neurons_response::{ClaimSwapNeuronsResult, ClaimedSwapNeurons, SwapNeuron}, - governance::Version, + governance::{Version, Versions}, governance_error::ErrorType, manage_neuron::{ self, claim_or_refresh, configure::Operation, AddNeuronPermissions, ClaimOrRefresh, @@ -39,12 +39,13 @@ use ic_sns_governance::{ }, neuron::{self, DissolveState, Followees}, proposal::Action, - Account as AccountProto, AddMaturityRequest, Ballot, ClaimSwapNeuronsError, - ClaimSwapNeuronsRequest, ClaimSwapNeuronsResponse, ClaimedSwapNeuronStatus, - DeregisterDappCanisters, Empty, GovernanceError, ManageNeuronResponse, - MintTokensRequest, MintTokensResponse, Motion, NervousSystemParameters, Neuron, - NeuronId, NeuronIds, NeuronPermission, NeuronPermissionList, NeuronPermissionType, - Proposal, ProposalData, ProposalId, RegisterDappCanisters, Vote, WaitForQuietState, + upgrade_journal_entry, Account as AccountProto, AddMaturityRequest, Ballot, + ClaimSwapNeuronsError, ClaimSwapNeuronsRequest, ClaimSwapNeuronsResponse, + ClaimedSwapNeuronStatus, DeregisterDappCanisters, Empty, GovernanceError, + ManageNeuronResponse, MintTokensRequest, MintTokensResponse, Motion, + NervousSystemParameters, Neuron, NeuronId, NeuronIds, NeuronPermission, + NeuronPermissionList, NeuronPermissionType, Proposal, ProposalData, ProposalId, + RegisterDappCanisters, UpgradeJournalEntry, Vote, WaitForQuietState, }, }, sns_upgrade::{ListUpgradeStep, ListUpgradeStepsResponse, SnsVersion}, @@ -2911,12 +2912,17 @@ async fn test_refresh_cached_upgrade_steps() { // Set up the fixture state { - let steps = expected_upgrade_steps + let steps: Vec<_> = expected_upgrade_steps .iter() .map(|v| ListUpgradeStep { version: Some(SnsVersion::from(v.clone())), }) .collect(); + canister_fixture + .environment_fixture + .push_mocked_canister_reply(ListUpgradeStepsResponse { + steps: steps.clone(), + }); canister_fixture .environment_fixture .push_mocked_canister_reply(ListUpgradeStepsResponse { steps }); @@ -3005,7 +3011,7 @@ async fn test_refresh_cached_upgrade_steps() { assert!(!should_refresh); } - // Check that the canister wants to refresh the cached_upgrade_steps after a while + // Check that the canister wants to refresh the cached_upgrade_steps after another second { canister_fixture.advance_time_by(1); let should_refresh = canister_fixture @@ -3013,6 +3019,39 @@ async fn test_refresh_cached_upgrade_steps() { .should_refresh_cached_upgrade_steps(); assert!(should_refresh); } + + // Refresh the cached upgrade steps again + { + canister_fixture + .governance + .refresh_cached_upgrade_steps() + .await; + } + + // Check that only one refresh has been recorded in the upgrade journal + { + let upgrade_journal = canister_fixture + .governance + .proto + .upgrade_journal + .clone() + .unwrap(); + assert_eq!( + upgrade_journal.entries, + vec![UpgradeJournalEntry { + // we advanced time by one second after the first refresh + timestamp_seconds: Some(DEFAULT_TEST_START_TIMESTAMP_SECONDS + 1), + // the event contains the upgrade steps + event: Some(upgrade_journal_entry::Event::UpgradeStepsRefreshed( + upgrade_journal_entry::UpgradeStepsRefreshed { + upgrade_steps: Some(Versions { + versions: expected_upgrade_steps + }), + } + )), + }] + ); + } } #[tokio::test]