Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
e99d857
test(xnet): exercise silently dropped responses on subnet deletion
mraszyk Jul 16, 2026
fecb354
test(xnet): exercise silently dropped responses in StateMachine subne…
mraszyk Jul 16, 2026
65633ae
Merge remote-tracking branch 'origin/master' into mraszyk/xnet-subnet…
mraszyk Jul 16, 2026
8229406
Merge branch 'master' into mraszyk/xnet-subnet-delete-response-drop
mraszyk Jul 17, 2026
a53392d
test: update subnet_splitting golden load estimates for universal can…
mraszyk Jul 17, 2026
013a944
Merge branch 'master' into mraszyk/xnet-subnet-delete-response-drop
mraszyk Jul 17, 2026
213e06e
Potential fix for pull request finding
mraszyk Jul 20, 2026
d799061
Match full remote="<id>" label in read_stream_gauge
mraszyk Jul 20, 2026
cf32447
Clarify canister_status is a management-canister call
mraszyk Jul 20, 2026
7ddba56
Merge remote-tracking branch 'origin/master' into mraszyk/xnet-subnet…
mraszyk Jul 20, 2026
23a223b
test(xnet): flatten and align runbook steps across subnet-delete tests
mraszyk Jul 20, 2026
93d69ad
test(xnet): clarify subnet-delete test comments per review
mraszyk Jul 20, 2026
80feb5e
test(xnet): clarify stream-full stopping condition in Step 7 comments
mraszyk Jul 20, 2026
31540fa
test(xnet): align Step 9 substep labels with the system test
mraszyk Jul 20, 2026
3e4ff95
test(xnet): assert dropped-response counter increase vs. baseline
mraszyk Jul 20, 2026
1799a7f
refactor(universal-canister): name management canister callee explicitly
mraszyk Jul 20, 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
1 change: 1 addition & 0 deletions Cargo.lock

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

343 changes: 298 additions & 45 deletions rs/state_machine_tests/tests/subnet_delete.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions rs/tests/message_routing/xnet/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ system_test(
runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS | NNS_CANISTER_RUNTIME_DEPS,
deps = [
# Keep sorted.
"//rs/config",
"//rs/nns/constants",
"//rs/registry/canister",
"//rs/registry/subnet_type",
Expand Down
1 change: 1 addition & 0 deletions rs/tests/message_routing/xnet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ candid = { workspace = true }
canister-test = { path = "../../../rust_canisters/canister_test" }
futures = { workspace = true }
ic-agent = { workspace = true }
ic-config = { path = "../../../config" }
ic-error-types = { path = "../../../../packages/ic-error-types" }
ic-management-canister-types-private = { path = "../../../types/management_canister_types" }
ic-nns-constants = { path = "../../../nns/constants" }
Expand Down
398 changes: 353 additions & 45 deletions rs/tests/message_routing/xnet/subnet_delete_test.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions rs/universal_canister/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,6 @@ try_from_u8!(
CostHttpRequestV2 = 97,
MsgCallerInfoData = 98,
MsgCallerInfoSigner = 99,
LoopUntilGlobalDataSet = 100,
}
);
55 changes: 55 additions & 0 deletions rs/universal_canister/impl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ pub struct TransformArg {
pub context: Vec<u8>,
}

/// Candid argument of the management canister's `canister_status` method.
#[derive(CandidType, Deserialize)]
pub struct CanisterIdRecord {
pub canister_id: Principal,
}

/// Encodes a `PushBytes` op (opcode byte, little-endian `u32` length, bytes)
/// into `out`, matching the encoding produced by the `PayloadBuilder`.
fn encode_push_bytes(out: &mut Vec<u8>, data: &[u8]) {
out.push(Ops::PushBytes as u8);
out.extend_from_slice(&(data.len() as u32).to_le_bytes());
out.extend_from_slice(data);
}

fn http_reply_with_body(body: &[u8]) -> Vec<u8> {
Encode!(&HttpResponse {
status: 200_u128,
Expand Down Expand Up @@ -513,6 +527,47 @@ fn eval(ops_bytes: OpsBytes) {
core::arch::wasm32::memory_grow(0, pages as usize);
}
}
Ops::LoopUntilGlobalDataSet => {
// Pop in the reverse of the builder's push order (trigger then reply).
let reply_data = stack.pop_blob();
let trigger = stack.pop_blob();
if get_global() == trigger {
// The awaited condition holds: reply and stop looping.
api::reply_data_append(&reply_data);
api::reply();
} else {
// The condition does not hold yet: make a management-canister
// `canister_status` call for this canister itself, and re-run
// this same op from both its reply and reject callbacks. This
// forms a loop across message boundaries that only ends once
// some other message sets the global data to `trigger`,
// thereby delaying the reply to the original caller.
let mut cb = Vec::new();
encode_push_bytes(&mut cb, &trigger);
encode_push_bytes(&mut cb, &reply_data);
cb.push(Ops::LoopUntilGlobalDataSet as u8);
// A single call yields either a reply or a reject (never
// both), so the same callback env can back both handlers.
let env = add_callback(cb);
let arg = Encode!(&CanisterIdRecord {
Comment thread
mraszyk marked this conversation as resolved.
canister_id: Principal::from_slice(&api::id()),
})
.unwrap();
api::call_new(
Principal::management_canister().as_slice(),
b"canister_status",
callback,
env,
callback,
env,
);
api::call_data_append(&arg);
let err_code = api::call_perform();
if err_code != 0 {
api::trap_with("call_perform failed in LoopUntilGlobalDataSet")
}
}
}
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions rs/universal_canister/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,21 @@ impl PayloadBuilder {
self
}

/// Loops indefinitely — each iteration performing a management-canister
/// `canister_status` call for the executing canister — until the canister's
/// global data equals `trigger`, and then replies with `reply`.
///
/// Because each loop iteration is an inter-canister call, the reply to the
/// caller is delayed across message boundaries until some other message
/// sets the global data to `trigger` (e.g. via `set_global_data`). This is
/// useful for delaying a response until an external condition is met.
pub fn loop_until_global_data_set(mut self, trigger: &[u8], reply: &[u8]) -> Self {
self = self.push_bytes(trigger);
self = self.push_bytes(reply);
self.0.push(Ops::LoopUntilGlobalDataSet as u8);
self
}

pub fn build(self) -> Vec<u8> {
self.0
}
Expand Down
Loading