Skip to content

Commit 7bf82b3

Browse files
feat(governance): Added new TakeCanisterSnapshot NNS proposal type. (#8283)
Disabled by flag. # Previous Work Previously, I tried to do this in [PR 8228], but during code review, radical changes were requested; hence this new PR. Concretely, in the previous PR, the new proposal type was implemented as an NnsFunction; whereas, here, it is implemented as a direct Action. The rationale for switching is that the it was requested that the topic of this TakeCanisterSnapshot depend on the target canister, which means that the Governance canister cannot treat the arguments of the proposal as an opaque blob (when determining the proposal's topic). [PR 8228]: #8228 # References Addresses [NNS1-4295]. [NNS1-4295]: https://dfinity.atlassian.net/browse/NNS1-4295 [👈 Previous PR][prev] | [Next PR 👉][next] [prev]: #8159 [next]: #8285
1 parent 43311b3 commit 7bf82b3

File tree

9 files changed

+167
-5
lines changed

9 files changed

+167
-5
lines changed

rs/nns/governance/api/src/types.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,16 @@ pub struct Motion {
405405
/// The text of the motion. Maximum 100kib.
406406
pub motion_text: String,
407407
}
408+
/// Take a snapshot of the state of a canister.
409+
#[derive(
410+
candid::CandidType, candid::Deserialize, serde::Serialize, Clone, PartialEq, Debug, Default,
411+
)]
412+
pub struct TakeCanisterSnapshot {
413+
/// The canister being snapshotted.
414+
pub canister_id: Option<PrincipalId>,
415+
/// If set, the existing snapshot with this content will be replaced.
416+
pub replace_snapshot: Option<Vec<u8>>,
417+
}
408418
/// For all Neurons controlled by the given principals, set their
409419
/// KYC status to `kyc_verified=true`.
410420
#[derive(
@@ -641,6 +651,8 @@ pub mod proposal {
641651
/// back to a healthy state, to recover from some kind of disaster, like
642652
/// a boot loop, or something like that.
643653
BlessAlternativeGuestOsVersion(super::BlessAlternativeGuestOsVersion),
654+
/// Take a canister snapshot.
655+
TakeCanisterSnapshot(super::TakeCanisterSnapshot),
644656
}
645657
}
646658
/// Empty message to use in oneof fields that represent empty
@@ -1399,6 +1411,7 @@ pub enum ProposalActionRequest {
13991411
UpdateCanisterSettings(UpdateCanisterSettings),
14001412
FulfillSubnetRentalRequest(FulfillSubnetRentalRequest),
14011413
BlessAlternativeGuestOsVersion(BlessAlternativeGuestOsVersion),
1414+
TakeCanisterSnapshot(TakeCanisterSnapshot),
14021415
}
14031416

14041417
#[derive(

rs/nns/governance/canister/governance.did

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Action = variant {
2222
Motion : Motion;
2323
FulfillSubnetRentalRequest : FulfillSubnetRentalRequest;
2424
BlessAlternativeGuestOsVersion : BlessAlternativeGuestOsVersion;
25+
TakeCanisterSnapshot : TakeCanisterSnapshot
2526
};
2627

2728
type AddHotKey = record {
@@ -1031,6 +1032,7 @@ type ProposalActionRequest = variant {
10311032
Motion : Motion;
10321033
FulfillSubnetRentalRequest : FulfillSubnetRentalRequest;
10331034
BlessAlternativeGuestOsVersion : BlessAlternativeGuestOsVersion;
1035+
TakeCanisterSnapshot : TakeCanisterSnapshot;
10341036
};
10351037

10361038
// Creates a rented subnet from a rental request (in the Subnet Rental
@@ -1099,6 +1101,10 @@ type GuestLaunchMeasurementMetadata = record {
10991101
kernel_cmdline : opt text;
11001102
};
11011103

1104+
type TakeCanisterSnapshot = record {
1105+
canister_id : opt principal;
1106+
replace_snapshot : opt blob;
1107+
};
11021108

11031109
type ProposalData = record {
11041110
id : opt ProposalId;

rs/nns/governance/proto/ic_nns_governance/pb/v1/governance.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,9 +707,19 @@ message Proposal {
707707
// Allow node operators to manually intervene in case of disaster to run
708708
// (NNS-approved) new software.
709709
BlessAlternativeGuestOsVersion bless_alternative_guest_os_version = 31;
710+
// Take a canister snapshot.
711+
TakeCanisterSnapshot take_canister_snapshot = 32;
710712
}
711713
}
712714

715+
// Take a canister snapshot.
716+
message TakeCanisterSnapshot {
717+
// The canister being snapshotted.
718+
ic_base_types.pb.v1.PrincipalId canister_id = 1;
719+
// If set, the existing snapshot with this content will be replaced.
720+
optional bytes replace_snapshot = 2;
721+
}
722+
713723
// Empty message to use in oneof fields that represent empty
714724
// enums.
715725
message Empty {}

rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ pub struct Proposal {
432432
/// take.
433433
#[prost(
434434
oneof = "proposal::Action",
435-
tags = "10, 12, 13, 14, 15, 16, 17, 18, 19, 21, 29, 22, 23, 24, 25, 26, 27, 28, 31"
435+
tags = "10, 12, 13, 14, 15, 16, 17, 18, 19, 21, 29, 22, 23, 24, 25, 26, 27, 28, 31, 32"
436436
)]
437437
pub action: ::core::option::Option<proposal::Action>,
438438
}
@@ -542,8 +542,29 @@ pub mod proposal {
542542
/// (NNS-approved) new software.
543543
#[prost(message, tag = "31")]
544544
BlessAlternativeGuestOsVersion(super::BlessAlternativeGuestOsVersion),
545+
/// Take a canister snapshot.
546+
#[prost(message, tag = "32")]
547+
TakeCanisterSnapshot(super::TakeCanisterSnapshot),
545548
}
546549
}
550+
/// Take a canister snapshot.
551+
#[derive(
552+
candid::CandidType,
553+
candid::Deserialize,
554+
serde::Serialize,
555+
comparable::Comparable,
556+
Clone,
557+
PartialEq,
558+
::prost::Message,
559+
)]
560+
pub struct TakeCanisterSnapshot {
561+
/// The canister being snapshotted.
562+
#[prost(message, optional, tag = "1")]
563+
pub canister_id: ::core::option::Option<::ic_base_types::PrincipalId>,
564+
/// If set, the existing snapshot with this content will be replaced.
565+
#[prost(bytes = "vec", optional, tag = "2")]
566+
pub replace_snapshot: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
567+
}
547568
/// Empty message to use in oneof fields that represent empty
548569
/// enums.
549570
#[derive(

rs/nns/governance/src/governance.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ use crate::{
4242
NeuronsFundSnapshot as NeuronsFundSnapshotPb, NnsFunction, NodeProvider, Proposal,
4343
ProposalData, ProposalRewardStatus, ProposalStatus, RestoreAgingSummary, RewardEvent,
4444
RewardNodeProvider, RewardNodeProviders, SettleNeuronsFundParticipationRequest,
45-
SettleNeuronsFundParticipationResponse, StopOrStartCanister, Tally, Topic,
46-
UpdateCanisterSettings, UpdateNodeProvider, Vote, VotingPowerEconomics,
45+
SettleNeuronsFundParticipationResponse, StopOrStartCanister, TakeCanisterSnapshot,
46+
Tally, Topic, UpdateCanisterSettings, UpdateNodeProvider, Vote, VotingPowerEconomics,
4747
WaitForQuietState, archived_monthly_node_provider_rewards,
4848
create_service_nervous_system::LedgerParameters,
4949
get_neurons_fund_audit_info_response,
@@ -512,6 +512,7 @@ impl Action {
512512
Action::BlessAlternativeGuestOsVersion(_) => {
513513
"ACTION_BLESS_ALTERNATIVE_GUEST_OS_VERSION"
514514
}
515+
Action::TakeCanisterSnapshot(_) => "ACTION_TAKE_CANISTER_SNAPSHOT",
515516
}
516517
}
517518
}
@@ -4174,6 +4175,10 @@ impl Governance {
41744175
pid,
41754176
bless_alternative_guest_os_version,
41764177
),
4178+
ValidProposalAction::TakeCanisterSnapshot(take_canister_snapshot) => {
4179+
self.perform_take_canister_snapshot(pid, take_canister_snapshot)
4180+
.await;
4181+
}
41774182
}
41784183
}
41794184

@@ -4254,6 +4259,17 @@ impl Governance {
42544259
self.set_proposal_execution_status(proposal_id, result);
42554260
}
42564261

4262+
async fn perform_take_canister_snapshot(
4263+
&mut self,
4264+
proposal_id: u64,
4265+
take_canister_snapshot: TakeCanisterSnapshot,
4266+
) {
4267+
let result = self
4268+
.perform_call_canister(proposal_id, take_canister_snapshot)
4269+
.await;
4270+
self.set_proposal_execution_status(proposal_id, result);
4271+
}
4272+
42574273
async fn perform_call_canister(
42584274
&mut self,
42594275
proposal_id: u64,
@@ -4771,6 +4787,9 @@ impl Governance {
47714787
ValidProposalAction::BlessAlternativeGuestOsVersion(
47724788
bless_alternative_guest_os_version,
47734789
) => bless_alternative_guest_os_version.validate(),
4790+
ValidProposalAction::TakeCanisterSnapshot(take_canister_snapshot) => {
4791+
take_canister_snapshot.validate()
4792+
}
47744793
}
47754794
}
47764795

rs/nns/governance/src/pb/conversions/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ impl From<api::proposal::Action> for pb::proposal::Action {
457457
api::proposal::Action::BlessAlternativeGuestOsVersion(v) => {
458458
pb::proposal::Action::BlessAlternativeGuestOsVersion(v.into())
459459
}
460+
api::proposal::Action::TakeCanisterSnapshot(v) => {
461+
pb::proposal::Action::TakeCanisterSnapshot(v.into())
462+
}
460463
}
461464
}
462465
}
@@ -509,6 +512,9 @@ impl From<api::ProposalActionRequest> for pb::proposal::Action {
509512
api::ProposalActionRequest::BlessAlternativeGuestOsVersion(v) => {
510513
pb::proposal::Action::BlessAlternativeGuestOsVersion(v.into())
511514
}
515+
api::ProposalActionRequest::TakeCanisterSnapshot(v) => {
516+
pb::proposal::Action::TakeCanisterSnapshot(v.into())
517+
}
512518
}
513519
}
514520
}
@@ -4141,3 +4147,21 @@ impl From<pb::MaturityDisbursement> for api::MaturityDisbursement {
41414147
}
41424148
}
41434149
}
4150+
4151+
impl From<pb::TakeCanisterSnapshot> for api::TakeCanisterSnapshot {
4152+
fn from(item: pb::TakeCanisterSnapshot) -> Self {
4153+
Self {
4154+
canister_id: item.canister_id,
4155+
replace_snapshot: item.replace_snapshot,
4156+
}
4157+
}
4158+
}
4159+
4160+
impl From<api::TakeCanisterSnapshot> for pb::TakeCanisterSnapshot {
4161+
fn from(item: api::TakeCanisterSnapshot) -> Self {
4162+
Self {
4163+
canister_id: item.canister_id,
4164+
replace_snapshot: item.replace_snapshot,
4165+
}
4166+
}
4167+
}

rs/nns/governance/src/pb/proposal_conversions.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ fn convert_action(
240240
pb::proposal::Action::BlessAlternativeGuestOsVersion(v) => {
241241
api::proposal::Action::BlessAlternativeGuestOsVersion(v.clone().into())
242242
}
243+
pb::proposal::Action::TakeCanisterSnapshot(v) => {
244+
api::proposal::Action::TakeCanisterSnapshot(v.clone().into())
245+
}
243246

244247
// The action types with potentially large fields need to be converted in a way that avoids
245248
// cloning the action first.

rs/nns/governance/src/proposals/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use crate::{
44
ApproveGenesisKyc, BlessAlternativeGuestOsVersion, CreateServiceNervousSystem,
55
DeregisterKnownNeuron, GovernanceError, InstallCode, KnownNeuron, ManageNeuron, Motion,
66
NetworkEconomics, ProposalData, RewardNodeProvider, RewardNodeProviders,
7-
SelfDescribingProposalAction, StopOrStartCanister, Topic, UpdateCanisterSettings, Vote,
8-
governance_error::ErrorType, proposal::Action,
7+
SelfDescribingProposalAction, StopOrStartCanister, TakeCanisterSnapshot, Topic,
8+
UpdateCanisterSettings, Vote, governance_error::ErrorType, proposal::Action,
99
},
1010
proposals::{
1111
add_or_remove_node_provider::ValidAddOrRemoveNodeProvider,
@@ -32,6 +32,7 @@ pub mod manage_neuron;
3232
pub mod register_known_neuron;
3333
pub mod self_describing;
3434
pub mod stop_or_start_canister;
35+
pub mod take_canister_snapshot;
3536
pub mod update_canister_settings;
3637

3738
mod decode_candid_args_to_self_describing_value;
@@ -57,6 +58,7 @@ pub(crate) enum ValidProposalAction {
5758
UpdateCanisterSettings(UpdateCanisterSettings),
5859
FulfillSubnetRentalRequest(ValidFulfillSubnetRentalRequest),
5960
BlessAlternativeGuestOsVersion(BlessAlternativeGuestOsVersion),
61+
TakeCanisterSnapshot(TakeCanisterSnapshot),
6062
}
6163

6264
impl TryFrom<Option<Action>> for ValidProposalAction {
@@ -117,6 +119,9 @@ impl TryFrom<Option<Action>> for ValidProposalAction {
117119
bless_alternative_guest_os_version,
118120
))
119121
}
122+
Action::TakeCanisterSnapshot(take_canister_snapshot) => Ok(
123+
ValidProposalAction::TakeCanisterSnapshot(take_canister_snapshot),
124+
),
120125

121126
// Obsolete actions
122127
Action::SetDefaultFollowees(_) => Err(GovernanceError::new_with_message(
@@ -161,6 +166,9 @@ impl ValidProposalAction {
161166
}
162167
ValidProposalAction::FulfillSubnetRentalRequest(_) => Topic::SubnetRental,
163168
ValidProposalAction::BlessAlternativeGuestOsVersion(_) => Topic::NodeAdmin,
169+
ValidProposalAction::TakeCanisterSnapshot(take_canister_snapshot) => {
170+
take_canister_snapshot.valid_topic()?
171+
}
164172
};
165173
Ok(topic)
166174
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::{
2+
pb::v1::{GovernanceError, TakeCanisterSnapshot, Topic},
3+
proposals::{call_canister::CallCanister, invalid_proposal_error, topic_to_manage_canister},
4+
};
5+
use candid::Encode;
6+
use ic_base_types::{CanisterId, PrincipalId};
7+
use ic_nns_constants::ROOT_CANISTER_ID;
8+
use ic_nns_handler_root_interface::TakeCanisterSnapshotRequest;
9+
10+
impl TakeCanisterSnapshot {
11+
pub fn validate(&self) -> Result<(), GovernanceError> {
12+
self.valid_canister_id()?;
13+
Ok(())
14+
}
15+
16+
pub fn valid_topic(&self) -> Result<Topic, GovernanceError> {
17+
let canister_id = self.valid_canister_id()?;
18+
Ok(topic_to_manage_canister(&canister_id))
19+
}
20+
21+
fn valid_canister_id(&self) -> Result<CanisterId, GovernanceError> {
22+
let canister_principal_id = self
23+
.canister_id
24+
.ok_or(invalid_proposal_error("Canister ID is required"))?;
25+
let canister_id = CanisterId::try_from(canister_principal_id)
26+
.map_err(|_| invalid_proposal_error("Invalid canister ID"))?;
27+
28+
Ok(canister_id)
29+
}
30+
}
31+
32+
impl CallCanister for TakeCanisterSnapshot {
33+
fn canister_and_function(&self) -> Result<(CanisterId, &str), GovernanceError> {
34+
Ok((ROOT_CANISTER_ID, "take_canister_snapshot"))
35+
}
36+
37+
fn payload(&self) -> Result<Vec<u8>, GovernanceError> {
38+
let args = convert_take_canister_snapshot_from_proposal_to_root_request(self)?;
39+
Encode!(&args)
40+
.map_err(|e| invalid_proposal_error(&format!("Failed to encode payload: {e}")))
41+
}
42+
}
43+
44+
pub fn convert_take_canister_snapshot_from_proposal_to_root_request(
45+
original: &TakeCanisterSnapshot,
46+
) -> Result<TakeCanisterSnapshotRequest, GovernanceError> {
47+
let TakeCanisterSnapshot {
48+
replace_snapshot,
49+
canister_id: _,
50+
} = original.clone();
51+
52+
let canister_id = PrincipalId::from(original.valid_canister_id()?);
53+
54+
Ok(TakeCanisterSnapshotRequest {
55+
canister_id,
56+
replace_snapshot,
57+
})
58+
}

0 commit comments

Comments
 (0)