Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
7 changes: 7 additions & 0 deletions rs/nns/governance/api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4335,6 +4335,11 @@ pub enum NnsFunction {
/// `SetupInitialDKG` requests without an explicit subnet id are routed to the
/// calling subnet (NNS).
SetDefaultInitialDkgSubnet = 58,
/// Merge a subnet into another subnet: the canister ID ranges of the source
/// subnet are merged into the canister ID range set of the destination subnet,
/// so that all canisters that used to be hosted by the source subnet are routed
/// to the destination subnet.
Comment thread
alin-at-dfinity marked this conversation as resolved.
Outdated
MergeSubnets = 59,
}
impl NnsFunction {
/// String value of the enum field names used in the ProtoBuf definition.
Expand Down Expand Up @@ -4423,6 +4428,7 @@ impl NnsFunction {
NnsFunction::SetDefaultInitialDkgSubnet => {
"NNS_FUNCTION_SET_DEFAULT_INITIAL_DKG_SUBNET"
}
NnsFunction::MergeSubnets => "NNS_FUNCTION_MERGE_SUBNETS",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
Expand Down Expand Up @@ -4506,6 +4512,7 @@ impl NnsFunction {
"NNS_FUNCTION_SPLIT_SUBNET" => Some(Self::SplitSubnet),
"NNS_FUNCTION_DELETE_SUBNET" => Some(Self::DeleteSubnet),
"NNS_FUNCTION_SET_DEFAULT_INITIAL_DKG_SUBNET" => Some(Self::SetDefaultInitialDkgSubnet),
"NNS_FUNCTION_MERGE_SUBNETS" => Some(Self::MergeSubnets),
_ => None,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,12 @@ enum NnsFunction {
// `SetupInitialDKG` requests without an explicit subnet id are routed to the
// calling subnet (NNS).
NNS_FUNCTION_SET_DEFAULT_INITIAL_DKG_SUBNET = 58;

// Merge a subnet into another subnet: the canister ID ranges of the source
// subnet are merged into the canister ID range set of the destination subnet,
// so that all canisters that used to be hosted by the source subnet are routed
// to the destination subnet.
Comment thread
alin-at-dfinity marked this conversation as resolved.
Outdated
NNS_FUNCTION_MERGE_SUBNETS = 59;
}

// Payload of a proposal that calls a function on another NNS
Expand Down
7 changes: 7 additions & 0 deletions rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5550,6 +5550,11 @@ pub enum NnsFunction {
/// `SetupInitialDKG` requests without an explicit subnet id are routed to the
/// calling subnet (NNS).
SetDefaultInitialDkgSubnet = 58,
/// Merge a subnet into another subnet: the canister ID ranges of the source
/// subnet are merged into the canister ID range set of the destination subnet,
/// so that all canisters that used to be hosted by the source subnet are routed
/// to the destination subnet.
MergeSubnets = 59,
}
impl NnsFunction {
/// String value of the enum field names used in the ProtoBuf definition.
Expand Down Expand Up @@ -5626,6 +5631,7 @@ impl NnsFunction {
Self::SplitSubnet => "NNS_FUNCTION_SPLIT_SUBNET",
Self::DeleteSubnet => "NNS_FUNCTION_DELETE_SUBNET",
Self::SetDefaultInitialDkgSubnet => "NNS_FUNCTION_SET_DEFAULT_INITIAL_DKG_SUBNET",
Self::MergeSubnets => "NNS_FUNCTION_MERGE_SUBNETS",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
Expand Down Expand Up @@ -5709,6 +5715,7 @@ impl NnsFunction {
"NNS_FUNCTION_SPLIT_SUBNET" => Some(Self::SplitSubnet),
"NNS_FUNCTION_DELETE_SUBNET" => Some(Self::DeleteSubnet),
"NNS_FUNCTION_SET_DEFAULT_INITIAL_DKG_SUBNET" => Some(Self::SetDefaultInitialDkgSubnet),
"NNS_FUNCTION_MERGE_SUBNETS" => Some(Self::MergeSubnets),
_ => None,
}
}
Expand Down
2 changes: 2 additions & 0 deletions rs/nns/governance/src/pb/conversions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3942,6 +3942,7 @@ impl From<pb::NnsFunction> for api::NnsFunction {
api::NnsFunction::SetSubnetOperationalLevel
}
pb::NnsFunction::SplitSubnet => api::NnsFunction::SplitSubnet,
pb::NnsFunction::MergeSubnets => api::NnsFunction::MergeSubnets,
pb::NnsFunction::DeleteSubnet => api::NnsFunction::DeleteSubnet,
pb::NnsFunction::SetDefaultInitialDkgSubnet => {
api::NnsFunction::SetDefaultInitialDkgSubnet
Expand Down Expand Up @@ -4042,6 +4043,7 @@ impl From<api::NnsFunction> for pb::NnsFunction {
pb::NnsFunction::SetSubnetOperationalLevel
}
api::NnsFunction::SplitSubnet => pb::NnsFunction::SplitSubnet,
api::NnsFunction::MergeSubnets => pb::NnsFunction::MergeSubnets,
api::NnsFunction::DeleteSubnet => pb::NnsFunction::DeleteSubnet,
api::NnsFunction::SetDefaultInitialDkgSubnet => {
pb::NnsFunction::SetDefaultInitialDkgSubnet
Expand Down
14 changes: 13 additions & 1 deletion rs/nns/governance/src/proposals/execute_nns_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ pub enum ValidNnsFunction {
SplitSubnet,
DeleteSubnet,
SetDefaultInitialDkgSubnet,
MergeSubnets,
}

impl ValidNnsFunction {
Expand Down Expand Up @@ -592,6 +593,7 @@ impl ValidNnsFunction {
ValidNnsFunction::SetDefaultInitialDkgSubnet => {
(REGISTRY_CANISTER_ID, "set_default_initial_dkg_subnet")
}
ValidNnsFunction::MergeSubnets => (REGISTRY_CANISTER_ID, "merge_subnets"),
}
}

Expand Down Expand Up @@ -623,7 +625,8 @@ impl ValidNnsFunction {
| ValidNnsFunction::SetSubnetOperationalLevel
| ValidNnsFunction::SplitSubnet
| ValidNnsFunction::DeleteSubnet
| ValidNnsFunction::SetDefaultInitialDkgSubnet => Topic::SubnetManagement,
| ValidNnsFunction::SetDefaultInitialDkgSubnet
| ValidNnsFunction::MergeSubnets => Topic::SubnetManagement,

ValidNnsFunction::ReviseElectedGuestosVersions
| ValidNnsFunction::ReviseElectedHostosVersions => Topic::IcOsVersionElection,
Expand Down Expand Up @@ -714,6 +717,7 @@ impl ValidNnsFunction {
ValidNnsFunction::SplitSubnet => "Split subnet",
ValidNnsFunction::DeleteSubnet => "Delete Subnet",
ValidNnsFunction::SetDefaultInitialDkgSubnet => "Set Default Initial DKG Subnet",
ValidNnsFunction::MergeSubnets => "Merge subnets",
}
}

Expand Down Expand Up @@ -944,6 +948,13 @@ impl ValidNnsFunction {
calls are routed when no subnet is specified explicitly in the request. If unset, \
such requests are routed to the calling subnet (NNS)."
}
ValidNnsFunction::MergeSubnets => {
"Merge a subnet into another subnet. The canister ID ranges of the source subnet \
are merged into the canister ID range set of the destination subnet, so that all \
canisters that used to be hosted by the source subnet are routed to the \
destination subnet. Only the routing table is updated: neither subnet record is \
modified and the source subnet is not deleted."
}
Comment thread
alin-at-dfinity marked this conversation as resolved.
}
}
}
Expand Down Expand Up @@ -1033,6 +1044,7 @@ impl TryFrom<NnsFunction> for ValidNnsFunction {
Ok(ValidNnsFunction::SetSubnetOperationalLevel)
}
NnsFunction::SplitSubnet => Ok(ValidNnsFunction::SplitSubnet),
NnsFunction::MergeSubnets => Ok(ValidNnsFunction::MergeSubnets),
NnsFunction::DeleteSubnet => Ok(ValidNnsFunction::DeleteSubnet),
NnsFunction::SetDefaultInitialDkgSubnet => {
Ok(ValidNnsFunction::SetDefaultInitialDkgSubnet)
Expand Down
7 changes: 7 additions & 0 deletions rs/nns/governance/unreleased_changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ on the process that this file is part of, see

## Added

* Added a new `NnsFunction` variant `MergeSubnets`, which proposes to merge a
subnet into another subnet: the canister ID ranges of the source subnet are
merged into the canister ID range set of the destination subnet, so that all
canisters that used to be hosted by the source subnet are routed to the
destination subnet. Only the routing table is updated: neither subnet record
Comment thread
alin-at-dfinity marked this conversation as resolved.
Outdated
is modified and the source subnet is not deleted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, basically, this migrates all canisters from one subnet to another, right? I don't think that's what "merge" calls to mind, at least, for me it doesn't. I mean, I do not hate the name, so it's probably not worth changing it at this point, but if you are willing to entertain suggestions, how about "migrate all subnet canisters", or "evacuate subnet", or something? This would make it clearer that the two subnets survive; it's just the contents of one are dumped into another.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "migrate" is already overloaded to refer to (i) subnet splitting via the registry key canister_migrations and (ii) single canister ID migration (routing table change) orchestrated by a dedicated NNS canister. So I'd prefer to not use it in yet another context.

I suppose your confusion comes from the fact that the source subnet survives (it must be cleaned up by a separate proposal) so how about MergeSubnetCanisterRanges?

@daniel-wong-dfinity-org-twin daniel-wong-dfinity-org-twin Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do as you see fit.

Yes, the fact that the source subnet remains afterwards is what makes me think that "merge" is not so apt. When you merge two companies, the employees do not simply move from one to the other. Rather, you are left with just one company.

The existing "migrate" operation moves a canister from one subnet to another, right? Isn't that what's going on here, just a different number of canisters? To me, that makes "migrate" an even better choice. I don't see it as conflation; I see it as consistency.

There's also my other suggestion, "evacuate", which does not conjure images of the existing thing called "migrate". This more emphasizes that ALL canisters are moved out (but does not mention that they will all go to the same destination), which to me, is not as important as the fact that existing canisters are finding a new home, have an actual DESTINATION, not just left stranded in the middle of the street, as might happen if you evacuate a building due to fire. "migrate" is less chaotic. It implies a planned destination, not just a departure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping MergeSubnets. @alin-at-dfinity made the case here that "merging canister ranges" is the right vocabulary for this registry mutation, which is one step of the larger process, and the doc comments now say up front that the source subnet survives and is cleaned up by a separate proposal -- which was the substance of your objection. Happy to revisit if the operator-facing naming confuses proposal reviewers.


## Changed

## Deprecated
Expand Down
19 changes: 19 additions & 0 deletions rs/registry/canister/canister/canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ use registry_canister::{
firewall::{
AddFirewallRulesPayload, RemoveFirewallRulesPayload, UpdateFirewallRulesPayload,
},
merge_subnets::MergeSubnetsPayload,
node_management::{
do_remove_node_directly::RemoveNodeDirectlyPayload,
do_remove_nodes::RemoveNodesPayload,
Expand Down Expand Up @@ -1085,6 +1086,24 @@ fn reroute_canister_ranges_(payload: RerouteCanisterRangesPayload) {
recertify_registry();
}

#[unsafe(export_name = "canister_update merge_subnets")]
fn merge_subnets() {
check_caller_is_governance_and_log("merge_subnets");
over(candid_one, merge_subnets_);
Comment thread
mraszyk marked this conversation as resolved.
}

#[candid_method(update, rename = "merge_subnets")]
fn merge_subnets_(payload: MergeSubnetsPayload) {
registry_mut()
.merge_subnets(payload)
.unwrap_or_else(|error_message| {
trap_with(&format!(
"{LOG_PREFIX} Merge subnets failed: {error_message}"
))
});
Comment thread
daniel-wong-dfinity-org-twin marked this conversation as resolved.
recertify_registry();
}

#[unsafe(export_name = "canister_update split_subnet")]
fn split_subnet() {
check_caller_is_governance_and_log("split_subnet");
Expand Down
6 changes: 6 additions & 0 deletions rs/registry/canister/canister/registry.did
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ type IPv4Config = record {
ip_addr : text;
};

type MergeSubnetsPayload = record {
source_subnet : principal;
destination_subnet : principal;
};

type MigrateCanistersPayload = record {
canister_ids : vec principal;
target_subnet_id : principal;
Expand Down Expand Up @@ -646,6 +651,7 @@ service : {
get_node_providers_monthly_xdr_rewards : (opt GetNodeProvidersMonthlyXdrRewardsRequest) -> (GetNodeProvidersMonthlyXdrRewardsResponse) query;
get_subnet : (GetSubnetRequest) -> (GetSubnetResponse) query;
get_subnet_for_canister : (GetSubnetForCanisterRequest) -> (GetSubnetForCanisterResponse) query;
merge_subnets : (MergeSubnetsPayload) -> ();
migrate_canisters: (MigrateCanistersPayload) -> (MigrateCanistersResponse);
migrate_node_operator_directly : (MigrateNodeOperatorPayload) -> ();
prepare_canister_migration : (PrepareCanisterMigrationPayload) -> ();
Expand Down
6 changes: 6 additions & 0 deletions rs/registry/canister/canister/registry_test.did
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ type IPv4Config = record {
ip_addr : text;
};

type MergeSubnetsPayload = record {
source_subnet : principal;
destination_subnet : principal;
};

type MigrateCanistersPayload = record {
canister_ids : vec principal;
target_subnet_id : principal;
Expand Down Expand Up @@ -646,6 +651,7 @@ service : {
get_node_providers_monthly_xdr_rewards : (opt GetNodeProvidersMonthlyXdrRewardsRequest) -> (GetNodeProvidersMonthlyXdrRewardsResponse) query;
get_subnet : (GetSubnetRequest) -> (GetSubnetResponse) query;
get_subnet_for_canister : (GetSubnetForCanisterRequest) -> (GetSubnetForCanisterResponse) query;
merge_subnets : (MergeSubnetsPayload) -> ();
migrate_canisters: (MigrateCanistersPayload) -> (MigrateCanistersResponse);
migrate_node_operator_directly : (MigrateNodeOperatorPayload) -> ();
prepare_canister_migration : (PrepareCanisterMigrationPayload) -> ();
Expand Down
Loading
Loading