Skip to content

feat(execution): system API ic0.is_subnet_admin - #12063

Draft
pietrodimarco-dfinity wants to merge 1 commit into
masterfrom
pietro/ic0-is-subnet-admin
Draft

pietrodimarco-dfinity wants to merge 1 commit into
masterfrom
pietro/ic0-is-subnet-admin

Conversation

@pietrodimarco-dfinity

Copy link
Copy Markdown
Contributor

Summary

New system API:

ic0.is_subnet_admin : (src : I, size : I) -> (result : i32);

It returns 1 if the principal at src/size is a subnet admin of the subnet the canister runs on, and 0 otherwise. Like ic0.is_controller, it traps if the bytes are not a valid principal and can be called from every entry point.

Why. Since #11835, a cloud engine's subnet admins can manage every canister on the engine through the management canister. A canister's own code can't recognize them: ic0.is_controller reads only the canister's controller list, and subnet admins are not on it.

OpenCloud runs into this. Its engine-operator canister is a subnet admin of its engine, and it writes /.well-known/ic-domains and /.well-known/ii-alternative-origins into the certified-assets canisters of the engine's apps. certified-assets accepts writes only from controllers, so today the operator adds itself to each app's controller list with update_settings. Owners then see an extra controller, and if they remove it, the operator can no longer write the files.

With this API, certified-assets (or any canister) can accept subnet admins alongside its controllers.

Why a separate call rather than changing ic0.is_controller. Martin suggested this on Slack. ic0.is_controller keeps its literal meaning, and canisters opt in: an app's controller-only methods stay closed to subnet admins unless its code asks.

Behavior

  • Which subnets. Admins count only on subnets that can have them (can_have_subnet_admins): cloud engines and rented subnets, both on the free cost schedule. This is the same rule as ReplicatedState::get_own_subnet_admins, which the management canister uses. On every other subnet it returns 0, so a canister can call it anywhere.
  • No new state. It reads the own subnet's admins from the NetworkTopology that the sandbox already receives.
  • Cost. Same as ic0.is_controller: 1,000 instructions plus the size of the principal.
  • Query cache. A cached query result is not invalidated when the admin set changes. A non-replicated query can therefore answer from the old set for up to query_cache_max_expiry_time (10 minutes), as ic0.subnet_self_node_count can after a membership change. Replicated execution always sees the current set.

Open questions

  • Rented subnets. It also returns 1 for the admins of a rented subnet, because they are subnet admins there. That mirrors get_own_subnet_admins, even though on rented subnets they get fewer management rights than on cloud engines. If it should count cloud engines only, that is a one-line change.
  • Spec. It needs an interface-spec entry in developer-docs, like feat: ic0_subnet_self_node_count #11132. Proposed text: ic0.is_subnet_admin : (src : I, size : I) -> (result : i32) checks whether the principal identified by src/size is one of the subnet admins of the subnet the canister is running on. If yes, it returns 1, otherwise 0. Only cloud engines and rented subnets have subnet admins; on any other subnet it returns 0. It traps if src/size does not identify a valid principal. Available in every context, like ic0.is_controller.
  • Universal canister. I did not add an op for it; the new tests use WAT. Happy to add one.

Testing

  • hypervisor.rs, 4 new tests. Each call goes through both an update and a non-replicated query, which must agree.
    • On a cloud engine: the admin gets 1, while the canister's controller and an unrelated principal get 0.
    • On a rented subnet: the admin gets 1.
    • On an application subnet with the normal cost schedule and admins in its topology: 0.
    • An invalid principal gets CanisterContractViolation.
  • embedders:
    • availability in every context, like ic0.is_controller (system_api_availability);
    • an invalid principal gets InvalidPrincipalId;
    • the Wasm64 import signature (wasm64_import_system_api_functions).
  • query_cache_future_proof_test lists the new call.

Run locally with cargo, not bazel:

  • ic-embedders: system_api, wasmtime_embedder and sandbox_safe_system_state all pass (145 tests).
  • ic-execution-environment hypervisor: 335 pass. 3 fail (call_perform_checks_freezing_threshold_in_update, ic0_canister_version, ic0_msg_caller_size_works_in_heartbeat) and fail the same way on master: cargo gave them a universal canister I built locally rather than the bazel one.
  • query_cache_future_proof_test passes.
  • cargo clippy --no-deps -D warnings on the three touched crates is clean.

🤖 Generated with Claude Code

`ic0.is_subnet_admin : (src : I, size : I) -> (result : i32)` returns 1 if
the principal is a subnet admin of the subnet the canister runs on, and 0
otherwise. Admins count only on subnets that can have them (cloud engines and
rented subnets), as in `ReplicatedState::get_own_subnet_admins`. Like
`ic0.is_controller`, it traps on an invalid principal and is available in
every context.

It lets a canister opt in to trusting the subnet admins, who can already
manage every canister on a cloud engine through the management canister,
without changing what `ic0.is_controller` means.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the feat label Nov 10, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant