Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
df16f59
feat: Record in-progress ingress messages after a subnet merge
mraszyk Aug 28, 2026
ef25fc8
feat(registry): add merge_subnets endpoint
mraszyk Aug 28, 2026
608b73f
test: drain a subnet that is "cooling down"
mraszyk Aug 28, 2026
f16e396
test: drain long-running calls from a subnet that is "cooling down"
mraszyk Aug 31, 2026
6d1acc9
merge: branch 'mraszyk/merge-subnets' into 'mraszyk/subnet-cooling-do…
mraszyk Aug 31, 2026
80680be
merge: branch 'mraszyk/subnet-merge-ingress-history' into 'mraszyk/su…
mraszyk Aug 31, 2026
46f16bd
feat: a MergeSubnets proposal performs the registry side of a subnet …
mraszyk Aug 31, 2026
691f033
feat(state_tool): print the batch time of a checkpoint
mraszyk Aug 31, 2026
e28d5ae
feat(consensus): report at info level that a halted subnet delivers n…
mraszyk Aug 31, 2026
bdd0bc9
test: merge a subnet that is "cooling down" into another subnet
mraszyk Aug 31, 2026
f349b14
Merge branch 'master' into mraszyk/subnet-cooling-down-test
mraszyk Aug 31, 2026
2e69e0d
test: cover more of what a subnet merge has to carry over
mraszyk Aug 31, 2026
76ecda4
feat: a MergeSubnets proposal performs the registry side of a subnet …
mraszyk Aug 31, 2026
d8126dc
Merge remote-tracking branch 'origin/master' into mraszyk/merge-subnets
mraszyk Sep 1, 2026
4b43061
chore(governance): changelog entry for the MergeSubnets proposal type
mraszyk Sep 1, 2026
155414e
fix: address Copilot review on the merge_subnets PR
mraszyk Sep 1, 2026
42a48c1
fix: clear stale chain key initializations from the merge recovery CUP
mraszyk Sep 1, 2026
7c8fe64
feat: restrict merge_subnets to the routing table change
mraszyk Sep 1, 2026
7aad10b
merge: branch 'mraszyk/merge-subnets' into 'mraszyk/subnet-cooling-do…
mraszyk Sep 1, 2026
9ffb0a1
Merge remote-tracking branch 'origin/master' into mraszyk/subnet-cool…
mraszyk Sep 1, 2026
ef42adb
fix: drive the subnet merge test's driver calls on the test's own run…
mraszyk Sep 1, 2026
7662532
Merge remote-tracking branch 'origin/master' into mraszyk/subnet-cool…
mraszyk Sep 7, 2026
5213e13
feat(state_tool): assemble the merged state of a subnet merge
mraszyk Sep 7, 2026
30b3b8a
Merge branch 'mraszyk/state-tool-merge' into mraszyk/subnet-cooling-d…
mraszyk Sep 7, 2026
a0168dc
test: assemble the merged state with `state-tool merge`
mraszyk Sep 7, 2026
a1e4ff5
feat(state_tool): assemble the merged state of a subnet merge
mraszyk Sep 7, 2026
cc647f9
test: upload the merged state with the recovery upload step
mraszyk Sep 7, 2026
8333ff9
fix(state_tool): address the review of the merge command
mraszyk Sep 7, 2026
099542a
fix(state_tool): assemble the merged checkpoint out of the way
mraszyk Sep 7, 2026
850ea16
fix(state_tool): remove the merged checkpoint if the merge fails afte…
mraszyk Sep 7, 2026
2ffd4d2
Merge branch 'mraszyk/state-tool-merge' into mraszyk/subnet-cooling-d…
mraszyk Sep 7, 2026
91d2fdb
refactor(state_tool): trim the merge command
mraszyk Sep 7, 2026
1c95d06
Merge branch 'mraszyk/state-tool-merge' into mraszyk/subnet-cooling-d…
mraszyk Sep 7, 2026
67a25be
fix(state_tool): claim the staging directory by creating it
mraszyk Sep 7, 2026
c8975db
Merge branch 'mraszyk/state-tool-merge' into mraszyk/subnet-cooling-d…
mraszyk Sep 7, 2026
6bb1138
Merge remote-tracking branch 'origin/master' into mraszyk/subnet-cool…
mraszyk Sep 8, 2026
7528aef
Merge remote-tracking branch 'origin/master' into mraszyk/subnet-cool…
mraszyk Sep 9, 2026
7bd2c1b
Merge remote-tracking branch 'origin/master' into mraszyk/subnet-cool…
mraszyk Sep 9, 2026
fd654f0
test: check the canisters of a cooling down subnet through the subnet…
mraszyk Sep 9, 2026
fa06f5f
revert(state_tool): take master's version of the merge command
mraszyk Sep 9, 2026
ef027ec
feat(consensus): report the consensus status as a metric
mraszyk Sep 9, 2026
a7ce570
test: wait for the CUP a subnet halts at rather than for the halt itself
mraszyk Sep 11, 2026
fdd53d0
feat(recovery): a subnet merging tool, and drive the system test thro…
mraszyk Sep 11, 2026
641bcf6
refactor(recovery): share the common helpers of the subnet reshaping …
mraszyk Sep 11, 2026
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
64 changes: 59 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,9 @@ members = [
"rs/protobuf/generator",
"rs/query_stats",
"rs/recovery",
"rs/recovery/subnet_merging",
"rs/recovery/subnet_splitting",
"rs/recovery/subnet_tools",
"rs/registry/admin",
"rs/registry/admin-derive",
"rs/registry/canister",
Expand Down
2 changes: 1 addition & 1 deletion rs/cup_explorer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub async fn get_catchup_content(url: &Url) -> Result<Option<pb::CatchUpContent>
}

/// Fetches the CatchUp package, if it's present.
async fn get_cup(url: &Url) -> Result<Option<pb::CatchUpPackage>, String> {
pub async fn get_cup(url: &Url) -> Result<Option<pb::CatchUpPackage>, String> {
let agent = Agent::new(url.clone(), Sender::Anonymous);
agent
.query_cup_endpoint(None)
Expand Down
1 change: 1 addition & 0 deletions rs/recovery/src/app_subnet_recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ impl RecoveryIterator<StepType, StepTypeIter> for AppSubnetRecovery {
None,
self.params.initial_dkg_subnet_id,
self.params.chain_key_subnet_id,
/*time=*/ None,
)?))
}

Expand Down
9 changes: 7 additions & 2 deletions rs/recovery/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ impl Recovery {
registry_params: Option<RegistryParams>,
initial_dkg_subnet_id: Option<SubnetId>,
chain_key_subnet_id: Option<SubnetId>,
time: Option<SystemTime>,
) -> RecoveryResult<impl Step + use<>> {
let chain_key_config = chain_key_subnet_id
.map(|id| match self.registry_helper.get_chain_key_config(id) {
Expand Down Expand Up @@ -847,7 +848,11 @@ impl Recovery {
chain_key_config,
replacement_nodes,
registry_params,
SystemTime::now(),
// The block time the recovered subnet starts from. Defaults
// to now, which is what a recovery replaying up to the
// present wants; a subnet merge passes the time it computed
// from the states it merged instead.
time.unwrap_or_else(SystemTime::now),
),
})
}
Expand Down Expand Up @@ -1209,7 +1214,7 @@ pub fn get_available_nodes_heights_from_metrics(
}

/// Lookup node IDs and corresponding IP addresses of all members of the given subnet
fn get_member_node_ids_and_ips(
pub fn get_member_node_ids_and_ips(
registry_helper: &RegistryHelper,
subnet_id: SubnetId,
) -> RecoveryResult<BTreeMap<NodeId, IpAddr>> {
Expand Down
1 change: 1 addition & 0 deletions rs/recovery/src/nns_recovery_failover_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ impl RecoveryIterator<StepType, StepTypeIter> for NNSRecoveryFailoverNodes {
Some(registry_params),
None,
None,
/*time=*/ None,
)?))
} else {
Err(RecoveryError::StepSkipped)
Expand Down
23 changes: 23 additions & 0 deletions rs/recovery/src/registry_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@ impl RegistryHelper {
})
}

/// Returns the subnet record of the given subnet as of `registry_version`,
/// rather than as of the latest version [Self::get_subnet_record] reads.
///
/// Polls the [RegistryReplicator] first, as every other getter does: a
/// version that the local store has not caught up with yet is not readable,
/// and the caller may well be asking about one that was just created.
pub fn get_subnet_record_at_version(
&self,
subnet_id: SubnetId,
registry_version: RegistryVersion,
) -> RecoveryResult<Option<SubnetRecord>> {
let _ = self.latest_registry_version()?;

self.registry_client()
.get_subnet_record(subnet_id, registry_version)
.map_err(|err| {
RecoveryError::RegistryError(format!(
"Failed to get the record of subnet {subnet_id} at registry version \
{registry_version}: {err}"
))
})
}

/// Returns the [SubnetRecord] of the given subnet.
pub fn get_subnet_record(&self, subnet_id: SubnetId) -> VersionedRecoveryResult<SubnetRecord> {
self.get(|registry_version, registry_client| {
Expand Down
6 changes: 5 additions & 1 deletion rs/recovery/src/steps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,11 @@ impl Step for MergeCertificationPoolsStep {
}
}

pub(crate) struct DownloadIcDataStep {
/// Downloads data (the state, the consensus pool, ...) of a node into a
/// working directory. Public so that a tool that works with more than one
/// working directory, such as subnet merging, can direct the download at the
/// right one.
pub struct DownloadIcDataStep {
pub logger: Logger,
pub ssh_helper: SshHelper,
pub backup_dir: PathBuf,
Expand Down
58 changes: 58 additions & 0 deletions rs/recovery/subnet_merging/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")

DEPENDENCIES = [
# Keep sorted.
"//rs/cup_explorer",
"//rs/protobuf",
"//rs/recovery",
"//rs/recovery/subnet_tools",
"//rs/registry/helpers",
"//rs/registry/routing_table",
"//rs/registry/subnet_type",
"//rs/state_layout",
"//rs/types/base_types",
"//rs/types/types",
"@crate_index//:anyhow",
"@crate_index//:clap",
"@crate_index//:futures",
"@crate_index//:reqwest",
"@crate_index//:serde",
"@crate_index//:serde_json",
"@crate_index//:slog",
"@crate_index//:strum",
"@crate_index//:tokio",
"@crate_index//:url",
]

rust_library(
name = "subnet_merging",
srcs = glob(
["src/**/*.rs"],
exclude = ["src/main.rs"],
),
crate_name = "ic_subnet_merging",
proc_macro_deps = ["@crate_index//:strum_macros"],
version = "0.1.0",
visibility = ["//rs:system-tests-pkg"],
deps = DEPENDENCIES,
)

rust_binary(
name = "subnet-merging-tool",
srcs = ["src/main.rs"],
visibility = ["//rs:release-pkg"],
deps = DEPENDENCIES + [
# Keep sorted.
":subnet_merging",
"//rs/canister_sandbox:backend_lib",
],
)

rust_test(
name = "subnet_merging_tool_test",
crate = "subnet_merging",
deps = DEPENDENCIES + [
# Keep sorted.
"//rs/test_utilities/tmpdir",
],
)
38 changes: 38 additions & 0 deletions rs/recovery/subnet_merging/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[package]
name = "ic-subnet-merging"
version.workspace = true
authors.workspace = true
edition.workspace = true
description.workspace = true
documentation.workspace = true

[dependencies]
anyhow = { workspace = true }
clap = { workspace = true }
futures = { workspace = true }
ic-base-types = { path = "../../types/base_types/" }
ic-cup-explorer = { path = "../../cup_explorer" }
ic-protobuf = { path = "../../protobuf" }
ic-recovery = { path = "../" }
ic-registry-client-helpers = { path = "../../registry/helpers" }
ic-registry-routing-table = { path = "../../registry/routing_table" }
ic-registry-subnet-type = { path = "../../registry/subnet_type" }
ic-state-layout = { path = "../../state_layout" }
ic-subnet-tools = { path = "../subnet_tools" }
ic-types = { path = "../../types/types" }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
slog = { workspace = true }

strum = { workspace = true }
strum_macros = { workspace = true }
tokio = { workspace = true }
url = { workspace = true }

[dev-dependencies]
ic-test-utilities-tmpdir = { path = "../../test_utilities/tmpdir" }

[[bin]]
name = "subnet-merging-tool"
path = "src/main.rs"
Loading
Loading