Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d34142b
Added DeclareAlternativeReplicaVirtualMachineSoftwareSet.
daniel-wong-dfinity-org Dec 2, 2025
2d4fa51
validation. This was mostly done by Antigravity. Impressive. Much toi…
daniel-wong-dfinity-org Dec 2, 2025
4bae097
tests
daniel-wong-dfinity-org Dec 2, 2025
188bb8c
Added flag for new DeclareAlternativeReplicaVirtualMachineSoftwareSet…
daniel-wong-dfinity-org Dec 3, 2025
7ef5f29
Updated governance.did.
daniel-wong-dfinity-org Dec 3, 2025
ff64fbe
Moved a test help function so that it can be used in more places. Wil…
daniel-wong-dfinity-org Dec 3, 2025
0f9a5dd
metadata is optional.
daniel-wong-dfinity-org Dec 4, 2025
0f63ade
Renamed to BlessAlternativeGuestOsVersion.
daniel-wong-dfinity-org Dec 4, 2025
d085aa1
Renamed field to rootfs_hash.
daniel-wong-dfinity-org Dec 8, 2025
0212c2f
AI referenced a specific section in an AMD spec, but we cannot find i…
daniel-wong-dfinity-org Dec 8, 2025
650b669
Minor technical clarification about what a guest launch measurement is.
daniel-wong-dfinity-org Dec 8, 2025
6e70ea6
Mention that SHA-256 is used for rootfs_hash.
daniel-wong-dfinity-org Dec 8, 2025
75e292c
typo: removed extraneous "e".
daniel-wong-dfinity-org Dec 8, 2025
b16d22e
Removed some extraneous conversions.
daniel-wong-dfinity-org Dec 8, 2025
7a37116
tiny refactor: let Some(...) instead of match.
daniel-wong-dfinity-org Dec 8, 2025
135da77
Clarify what "base" means in `base_guest_launch_measurements`.
daniel-wong-dfinity-org Dec 8, 2025
f833b91
Use authoritative definition of GuestLaunchMeasurements et. al.
daniel-wong-dfinity-org Dec 8, 2025
4a74665
Move validation to rs/protobuf.
daniel-wong-dfinity-org Dec 8, 2025
634c5f6
lint
daniel-wong-dfinity-org Dec 8, 2025
1512aa2
Stuff explodes when you indent numbered lists. Because Markdown.
daniel-wong-dfinity-org Dec 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions rs/nns/governance/api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,18 @@ pub mod proposal {
/// charged for the use of computational resources (mainly, executing
/// instructions, storing data, network, etc.)
FulfillSubnetRentalRequest(super::FulfillSubnetRentalRequest),
/// The main use case for this is when the virtual machine (VM) where
/// replica runs is totally hosed such that it cannot be upgraded (and
/// therefore fixed) using the usual mechanisms. When the VM is started
/// with this alternative software, a signed copy of the ProposalInfo is
/// passed to the VM, and read at boot time to prove that NNS has
/// approved this alternative set of software. One of the main goals of
/// this alternative software would generally be to bring the system
/// back to a healthy state, to recover from some kind of disaster, like
/// a boot loop, or something like that.
DeclareAlternativeReplicaVirtualMachineSoftwareSet(
super::DeclareAlternativeReplicaVirtualMachineSoftwareSet,
),
}
}
/// Empty message to use in oneof fields that represent empty
Expand Down Expand Up @@ -1388,6 +1400,9 @@ pub enum ProposalActionRequest {
StopOrStartCanister(StopOrStartCanister),
UpdateCanisterSettings(UpdateCanisterSettings),
FulfillSubnetRentalRequest(FulfillSubnetRentalRequest),
DeclareAlternativeReplicaVirtualMachineSoftwareSet(
DeclareAlternativeReplicaVirtualMachineSoftwareSet,
),
}

#[derive(
Expand Down Expand Up @@ -2661,6 +2676,44 @@ pub struct FulfillSubnetRentalRequest {
pub node_ids: Option<Vec<PrincipalId>>,
pub replica_version_id: Option<String>,
}

#[derive(
candid::CandidType, candid::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Debug, Default,
)]
pub struct DeclareAlternativeReplicaVirtualMachineSoftwareSet {
pub chip_ids: Option<Vec<Vec<u8>>>,
pub hexidecimal_recovery_rootfs_fingerprint: Option<String>,
pub base_guest_launch_measurements: Option<GuestLaunchMeasurements>,
}

/// See also the definition of GuestLaunchMeasurements (plural!) in
/// rs/protobuf/def/registry/replica_version/v1/replica_version.proto
#[derive(
candid::CandidType, candid::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Debug, Default,
)]
pub struct GuestLaunchMeasurements {
pub guest_launch_measurements: Option<Vec<GuestLaunchMeasurement>>,
}

/// See also the definition of GuestLaunchMeasurement in
/// rs/protobuf/def/registry/replica_version/v1/replica_version.proto
#[derive(
candid::CandidType, candid::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Debug, Default,
)]
pub struct GuestLaunchMeasurement {
pub measurement: Option<Vec<u8>>,
pub metadata: Option<GuestLaunchMeasurementMetadata>,
}

/// See also the definition of GuestLaunchMeasurementMetadata in
/// rs/protobuf/def/registry/replica_version/v1/replica_version.proto
#[derive(
candid::CandidType, candid::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Debug, Default,
)]
pub struct GuestLaunchMeasurementMetadata {
pub kernel_cmdline: Option<String>,
}

/// This represents the whole NNS governance system. It contains all
/// information about the NNS governance system that must be kept
/// across upgrades of the NNS governance system.
Expand Down
35 changes: 35 additions & 0 deletions rs/nns/governance/canister/governance.did
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Action = variant {
AddOrRemoveNodeProvider : AddOrRemoveNodeProvider;
Motion : Motion;
FulfillSubnetRentalRequest : FulfillSubnetRentalRequest;
DeclareAlternativeReplicaVirtualMachineSoftwareSet : DeclareAlternativeReplicaVirtualMachineSoftwareSet;
};

type AddHotKey = record {
Expand Down Expand Up @@ -1029,6 +1030,7 @@ type ProposalActionRequest = variant {
AddOrRemoveNodeProvider : AddOrRemoveNodeProvider;
Motion : Motion;
FulfillSubnetRentalRequest : FulfillSubnetRentalRequest;
DeclareAlternativeReplicaVirtualMachineSoftwareSet : DeclareAlternativeReplicaVirtualMachineSoftwareSet;
};

// Creates a rented subnet from a rental request (in the Subnet Rental
Expand Down Expand Up @@ -1063,6 +1065,39 @@ type FulfillSubnetRentalRequest = record {
replica_version_id : opt text;
};

// Declares an approved set of alternative replica virtual machine software for
// disaster recovery purposes.
type DeclareAlternativeReplicaVirtualMachineSoftwareSet = record {
// AMD Secure Processor chip IDs that are allowed to run this software.
// Each chip ID must be exactly 64 bytes.
chip_ids : opt vec blob;

// Hexadecimal fingerprint of the recovery rootfs.
// Must contain only hexadecimal characters (0-9, A-F, a-f).
hexidecimal_recovery_rootfs_fingerprint : opt text;

// Base guest launch measurements for SEV-SNP attestation.
base_guest_launch_measurements : opt GuestLaunchMeasurements;
};

type GuestLaunchMeasurements = record {
guest_launch_measurements : opt vec GuestLaunchMeasurement;
};

type GuestLaunchMeasurement = record {
// SEV-SNP measurement (48 bytes).
measurement : opt blob;

// Metadata associated with the measurement.
metadata : opt GuestLaunchMeasurementMetadata;
};

type GuestLaunchMeasurementMetadata = record {
// Kernel command line used for this measurement.
kernel_cmdline : opt text;
};


type ProposalData = record {
id : opt ProposalId;
failure_reason : opt GovernanceError;
Expand Down
2 changes: 1 addition & 1 deletion rs/nns/governance/canister/governance_test.did.patch
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- rs/nns/governance/canister/governance.did
+++ rs/nns/governance/canister/governance_test.did
@@ -1379,4 +1379,7 @@
@@ -1413,4 +1413,7 @@
simulate_manage_neuron : (ManageNeuronRequest) -> (ManageNeuronResponse);
transfer_gtc_neuron : (NeuronId, NeuronId) -> (Result);
update_node_provider : (UpdateNodeProvider) -> (Result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,9 @@ message Proposal {
UpdateCanisterSettings update_canister_settings = 27;
// Create a rented subnet.
FulfillSubnetRentalRequest fulfill_subnet_rental_request = 28;
// Allow node operators to manually intervene in case of disaster to run
// (NNS-approved) new software.
DeclareAlternativeReplicaVirtualMachineSoftwareSet declare_alternative_replica_virtual_machine_software_set = 31;
}
}

Expand Down Expand Up @@ -1921,6 +1924,43 @@ message FulfillSubnetRentalRequest {
string replica_version_id = 2;
}

message DeclareAlternativeReplicaVirtualMachineSoftwareSet {
repeated bytes chip_ids = 1;
string hexidecimal_recovery_rootfs_fingerprint = 2;
GuestLaunchMeasurements base_guest_launch_measurements = 3;
}

// This is copied from rs/protobuf/.../replica_version.proto. This hack gets
// around how we tell Prost to slather #[derive(Comparable)] all over the place.
// That creates a viral requirement, but the rest of the world does not fulfill
// that requiremnt. Thus, we paint ourselves into this corner where we are not
// allowed to use the rest of the world, and must do unholy things like this.
//
// A less hacky way around this might be to use an alternative Prost
// configuration to derive Rust types that have the necessary
// #[derive(Comparable)] from the same replica_version.proto file, but for the
// time being, copying seems more or less okish.
message GuestLaunchMeasurements {
// A list of valid SEV-SNP measurements. One release can have multiple valid
// measurements, e.g. depending on the kernel command line used to launch the
// guest. Must be non-empty.
repeated GuestLaunchMeasurement guest_launch_measurements = 1;
}

// See remarks on GuestLaunchMeasurements (plural).
message GuestLaunchMeasurement {
// SEV-SNP measurement (48 bytes)
bytes measurement = 1;
// Metadata about how the measurement was obtained
GuestLaunchMeasurementMetadata metadata = 2;
}

// See remarks on GuestLaunchMeasurements (plural).
message GuestLaunchMeasurementMetadata {
// Kernel command line string used to launch the guest
string kernel_cmdline = 1;
}

// This represents the whole NNS governance system. It contains all
// information about the NNS governance system that must be kept
// across upgrades of the NNS governance system.
Expand Down
84 changes: 83 additions & 1 deletion rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ pub struct Proposal {
/// take.
#[prost(
oneof = "proposal::Action",
tags = "10, 12, 13, 14, 15, 16, 17, 18, 19, 21, 29, 22, 23, 24, 25, 26, 27, 28"
tags = "10, 12, 13, 14, 15, 16, 17, 18, 19, 21, 29, 22, 23, 24, 25, 26, 27, 28, 31"
)]
pub action: ::core::option::Option<proposal::Action>,
}
Expand Down Expand Up @@ -538,6 +538,12 @@ pub mod proposal {
/// Create a rented subnet.
#[prost(message, tag = "28")]
FulfillSubnetRentalRequest(super::FulfillSubnetRentalRequest),
/// Allow node operators to manually intervene in case of disaster to run
/// (NNS-approved) new software.
#[prost(message, tag = "31")]
DeclareAlternativeReplicaVirtualMachineSoftwareSet(
super::DeclareAlternativeReplicaVirtualMachineSoftwareSet,
),
}
}
/// Empty message to use in oneof fields that represent empty
Expand Down Expand Up @@ -2786,6 +2792,82 @@ pub struct FulfillSubnetRentalRequest {
#[prost(string, tag = "2")]
pub replica_version_id: ::prost::alloc::string::String,
}
#[derive(
candid::CandidType,
candid::Deserialize,
serde::Serialize,
comparable::Comparable,
Clone,
PartialEq,
::prost::Message,
)]
pub struct DeclareAlternativeReplicaVirtualMachineSoftwareSet {
#[prost(bytes = "vec", repeated, tag = "1")]
pub chip_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(string, tag = "2")]
pub hexidecimal_recovery_rootfs_fingerprint: ::prost::alloc::string::String,
#[prost(message, optional, tag = "3")]
pub base_guest_launch_measurements: ::core::option::Option<GuestLaunchMeasurements>,
}
/// This is copied from rs/protobuf/.../replica_version.proto. This hack gets
/// around how we tell Prost to slather #\[derive(Comparable)\] all over the place.
/// That creates a viral requirement, but the rest of the world does not fulfill
/// that requiremnt. Thus, we paint ourselves into this corner where we are not
/// allowed to use the rest of the world, and must do unholy things like this.
///
/// A less hacky way around this might be to use an alternative Prost
/// configuration to derive Rust types that have the necessary
/// #\[derive(Comparable)\] from the same replica_version.proto file, but for the
/// time being, copying seems more or less okish.
#[derive(
candid::CandidType,
candid::Deserialize,
serde::Serialize,
comparable::Comparable,
Clone,
PartialEq,
::prost::Message,
)]
pub struct GuestLaunchMeasurements {
/// A list of valid SEV-SNP measurements. One release can have multiple valid
/// measurements, e.g. depending on the kernel command line used to launch the
/// guest. Must be non-empty.
#[prost(message, repeated, tag = "1")]
pub guest_launch_measurements: ::prost::alloc::vec::Vec<GuestLaunchMeasurement>,
}
/// See remarks on GuestLaunchMeasurements (plural).
#[derive(
candid::CandidType,
candid::Deserialize,
serde::Serialize,
comparable::Comparable,
Clone,
PartialEq,
::prost::Message,
)]
pub struct GuestLaunchMeasurement {
/// SEV-SNP measurement (48 bytes)
#[prost(bytes = "vec", tag = "1")]
pub measurement: ::prost::alloc::vec::Vec<u8>,
/// Metadata about how the measurement was obtained
#[prost(message, optional, tag = "2")]
pub metadata: ::core::option::Option<GuestLaunchMeasurementMetadata>,
}
/// See remarks on GuestLaunchMeasurements (plural).
#[derive(
candid::CandidType,
candid::Deserialize,
serde::Serialize,
comparable::Comparable,
Clone,
PartialEq,
::prost::Message,
)]
pub struct GuestLaunchMeasurementMetadata {
/// Kernel command line string used to launch the guest
#[prost(string, tag = "1")]
pub kernel_cmdline: ::prost::alloc::string::String,
}
/// This represents the whole NNS governance system. It contains all
/// information about the NNS governance system that must be kept
/// across upgrades of the NNS governance system.
Expand Down
24 changes: 23 additions & 1 deletion rs/nns/governance/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ use crate::{
self,
proposal_conversions::{ProposalDisplayOptions, proposal_data_to_info},
v1::{
ArchivedMonthlyNodeProviderRewards, Ballot, CreateServiceNervousSystem, Followees,
ArchivedMonthlyNodeProviderRewards, Ballot, CreateServiceNervousSystem,
DeclareAlternativeReplicaVirtualMachineSoftwareSet, Followees,
FulfillSubnetRentalRequest, GetNeuronsFundAuditInfoRequest,
GetNeuronsFundAuditInfoResponse, Governance as GovernanceProto, GovernanceError,
InstallCode, KnownNeuron, ListKnownNeuronsResponse, ManageNeuron,
Expand Down Expand Up @@ -511,6 +512,9 @@ impl Action {
Action::StopOrStartCanister(_) => "ACTION_STOP_OR_START_CANISTER",
Action::UpdateCanisterSettings(_) => "ACTION_UPDATE_CANISTER_SETTINGS",
Action::FulfillSubnetRentalRequest(_) => "ACTION_FULFILL_SUBNET_RENTAL_REQUEST",
Action::DeclareAlternativeReplicaVirtualMachineSoftwareSet(_) => {
"ACTION_DECLARE_ALTERNATIVE_REPLICA_VIRTUAL_MACHINE_SOFTWARE_SET"
}
}
}
}
Expand Down Expand Up @@ -4237,6 +4241,12 @@ impl Governance {
self.perform_fulfill_subnet_rental_request(pid, fulfill_subnet_rental_request)
.await
}
ValidProposalAction::DeclareAlternativeReplicaVirtualMachineSoftwareSet(
declare_alternative_virtual_machine_software_set,
) => self.perform_declare_alternative_virtual_machine_software_set(
pid,
declare_alternative_virtual_machine_software_set,
),
}
}

Expand Down Expand Up @@ -4308,6 +4318,15 @@ impl Governance {
self.set_proposal_execution_status(proposal_id, result);
}

fn perform_declare_alternative_virtual_machine_software_set(
&mut self,
proposal_id: u64,
declare_alternative_virtual_machine_software_set: DeclareAlternativeReplicaVirtualMachineSoftwareSet,
) {
let result = declare_alternative_virtual_machine_software_set.execute();
self.set_proposal_execution_status(proposal_id, result);
}

async fn perform_call_canister(
&mut self,
proposal_id: u64,
Expand Down Expand Up @@ -4824,6 +4843,9 @@ impl Governance {
ValidProposalAction::DeregisterKnownNeuron(deregister_known_neuron) => {
deregister_known_neuron.validate(&self.neuron_store)
}
ValidProposalAction::DeclareAlternativeReplicaVirtualMachineSoftwareSet(
declare_alternative_virtual_machine_software_set,
) => declare_alternative_virtual_machine_software_set.validate(),
}
}

Expand Down
25 changes: 25 additions & 0 deletions rs/nns/governance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ thread_local! {

static ENABLE_SELF_DESCIBING_PROPOSAL_ACTIONS: Cell<bool>
= const { Cell::new(false) };

static ENABLE_DECLARE_ALTERNATIVE_REPLICA_VIRTUAL_MACHINE_SOFTWARE_SET_PROPOSALS: Cell<bool>
= const { Cell::new(cfg!(feature = "test")) };
}

thread_local! {
Expand Down Expand Up @@ -284,6 +287,28 @@ pub fn temporarily_disable_self_describing_proposal_actions() -> Temporary {
Temporary::new(&ENABLE_SELF_DESCIBING_PROPOSAL_ACTIONS, false)
}

pub fn are_declare_alternative_replica_virtual_machine_software_set_proposals_enabled() -> bool {
ENABLE_DECLARE_ALTERNATIVE_REPLICA_VIRTUAL_MACHINE_SOFTWARE_SET_PROPOSALS.get()
}

#[cfg(any(test, feature = "canbench-rs", feature = "test"))]
pub fn temporarily_enable_declare_alternative_replica_virtual_machine_software_set_proposals()
-> Temporary {
Temporary::new(
&ENABLE_DECLARE_ALTERNATIVE_REPLICA_VIRTUAL_MACHINE_SOFTWARE_SET_PROPOSALS,
true,
)
}

#[cfg(any(test, feature = "canbench-rs", feature = "test"))]
pub fn temporarily_disable_declare_alternative_replica_virtual_machine_software_set_proposals()
-> Temporary {
Temporary::new(
&ENABLE_DECLARE_ALTERNATIVE_REPLICA_VIRTUAL_MACHINE_SOFTWARE_SET_PROPOSALS,
false,
)
}

pub fn decoder_config() -> DecoderConfig {
let mut config = DecoderConfig::new();
config.set_skipping_quota(DEFAULT_SKIPPING_QUOTA);
Expand Down
Loading
Loading