Skip to content

Commit cdacb9b

Browse files
authored
feat: allow composite queries to call the management canister query methods (#342)
Reflect dfinity/ic#10987: composite query methods and their callbacks can call the management canister query methods `canister_status`, `canister_metrics`, `fetch_canister_logs`, and `list_canisters`. Such a call is not routed based on the method name and the argument: it is always executed against the state of the subnet hosting the calling canister and can thus only target canisters hosted by that subnet. The access control is the same as for the corresponding query call submitted by a user, with the calling canister as the caller. Calls to all other management canister methods are rejected.
1 parent 520d0ba commit cdacb9b

5 files changed

Lines changed: 74 additions & 16 deletions

File tree

docs/concepts/canisters.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ Key constraints:
5151
- **Ingress only**: only external clients (browsers, CLI tools) can invoke composite queries. Other canisters cannot call them.
5252
- **No replicated mode**: unlike regular queries, composite queries cannot be executed as update calls for stronger authenticity.
5353

54+
A composite query can also call the [management canister](../references/management-canister.md) query methods `canister_status`, `canister_metrics`, `fetch_canister_logs`, and `list_canisters`. These calls are answered from the state of the calling canister's own subnet, so they can only target canisters on that subnet, and the calling canister must be permitted to read that data (for example, be a controller of the target canister).
55+
5456
## Memory model
5557

5658
Each canister has two storage regions:

docs/references/ic-interface-spec/abstract-behavior.md

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4574,14 +4574,56 @@ for calls to `/api/v3/subnet/<ESID>/read_state`.
45744574

45754575
#### Query call {#query-call}
45764576

4577-
This section specifies query calls `Q` whose `Q.canister_id` is a non-empty canister `S.canisters[Q.canister_id]`. Query calls to the management canister, i.e., `Q.canister_id = ic_principal`, are specified in Sections [Canister status](#ic-management-canister-canister-status), [Canister logs](#ic-mgmt-canister-fetch-canister-logs), and [List canisters](#ic-mgmt-canister-list-canisters).
4577+
This section specifies query calls `Q` whose `Q.canister_id` is a non-empty canister `S.canisters[Q.canister_id]`. Query calls to the management canister, i.e., `Q.canister_id = ic_principal`, are specified in Sections [Canister status](#ic-management-canister-canister-status), [Canister metrics](#ic-management-canister-canister-metrics), [Canister logs](#ic-mgmt-canister-fetch-canister-logs), and [List canisters](#ic-mgmt-canister-list-canisters).
45784578

45794579
Canister query calls to `/api/v3/canister/<ECID>/query` can be executed directly. They can only be executed against non-empty canisters which have a status of `Running` and are also not frozen.
45804580

45814581
In query and composite query methods evaluated on the target canister of the query call, a certificate is provided to the canister that is valid, contains a current state tree (or "recent enough"; the specification is currently vague about how old the certificate may be), and reveals the canister's [Certified Data](./canister-interface.md#system-api-certified-data).
45824582

45834583
Composite query methods can call query methods and composite query methods up to a maximum depth `MAX_CALL_DEPTH_COMPOSITE_QUERY` of the call graph. The total amount of cycles consumed by executing a (composite) query method and all (transitive) calls it makes must be at most `MAX_CYCLES_PER_QUERY`. This limit applies in addition to the limit `MAX_CYCLES_PER_MESSAGE` for executing a single (composite) query method and `MAX_CYCLES_PER_RESPONSE` for executing a single callback of a (composite) query method.
45844584

4585+
Composite query methods and their callbacks can also call the management canister query methods `canister_status`, `canister_metrics`, `fetch_canister_logs`, and `list_canisters`. Unlike calls to the management canister in replicated mode, such a call is not routed based on the method name and the argument: it is always executed against the state of the subnet hosting the calling canister and can thus only target canisters hosted by that subnet. Who is allowed to call these methods is determined in the same way as for the corresponding query call submitted by a user, with the calling canister as the caller. Calls to all other management canister methods are rejected. Calls to the management canister do not contribute to the depth of the call graph, but the cycles consumed while producing their responses count towards `MAX_CYCLES_PER_QUERY`.
4586+
4587+
We define an auxiliary function that handles calls from composite query methods to the management canister. It returns the response to the call and the amount of cycles consumed while producing that response. The reject code and reject message of a reject response are implementation-specific.
4588+
```
4589+
management_canister_query(S, Caller, Method_name, Arg) =
4590+
let Cycles_used = <implementation-specific>
4591+
if Method_name = 'canister_status' and Arg = candid(A) and
4592+
S.canister_subnet[A.canister_id].subnet_id = S.canister_subnet[Caller].subnet_id and
4593+
((Caller = A.canister_id)
4594+
or
4595+
(Caller ∈ S.subnet_admins[S.canister_subnet[A.canister_id]])
4596+
or
4597+
(S.canister_status_visibility[A.canister_id] = Public)
4598+
or
4599+
(S.canister_status_visibility[A.canister_id] = Controllers and Caller ∈ S.controllers[A.canister_id])
4600+
or
4601+
(S.canister_status_visibility[A.canister_id] = AllowedViewers Principals and (Caller ∈ S.controllers[A.canister_id] or Caller ∈ Principals)))
4602+
then
4603+
Return (Reply (candid(canister_status(S, A.canister_id))), Cycles_used)
4604+
if Method_name = 'canister_metrics' and Arg = candid(A) and
4605+
S.canister_subnet[A.canister_id].subnet_id = S.canister_subnet[Caller].subnet_id and
4606+
Caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]]
4607+
then
4608+
Return (Reply (candid(<implementation-specific>)), Cycles_used)
4609+
if Method_name = 'fetch_canister_logs' and Arg = candid(A) and
4610+
S.canister_subnet[A.canister_id].subnet_id = S.canister_subnet[Caller].subnet_id and
4611+
((S.canister_log_visibility[A.canister_id] = Public)
4612+
or
4613+
(S.canister_log_visibility[A.canister_id] = Controllers and Caller ∈ S.controllers[A.canister_id])
4614+
or
4615+
(S.canister_log_visibility[A.canister_id] = AllowedViewers Principals and (Caller ∈ S.controllers[A.canister_id] or Caller ∈ Principals)))
4616+
then
4617+
Return (Reply (candid(S.canister_logs[A.canister_id])), Cycles_used)
4618+
if Method_name = 'list_canisters' and
4619+
Caller ∈ S.subnet_admins[S.canister_subnet[Caller]]
4620+
then
4621+
// CanisterIdRanges is the list of all canister IDs on the subnet S.canister_subnet[Caller]
4622+
// encoded as consecutive canister ID ranges (excluding deleted canisters)
4623+
Return (Reply (candid({canisters: CanisterIdRanges})), Cycles_used)
4624+
Return (Reject (<implementation-specific>, <implementation-specific>), Cycles_used)
4625+
```
4626+
45854627
We define an auxiliary method that handles calls from composite query methods by performing a call graph traversal. It can also be (trivially) invoked for query methods that do not make further calls.
45864628
```
45874629
composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Caller_info_data, Caller_info_signer, Canister_id, Method_name, Arg) =
@@ -4641,12 +4683,17 @@ composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Caller_info_d
46414683
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // max call graph depth exceeded
46424684
let Calls' · Call · Calls'' = Calls
46434685
Calls := Calls' · Calls''
4644-
if S.canister_subnet[Canister_id].subnet_id ≠ S.canister_subnet[Call.callee].subnet_id
4686+
if Call.callee = ic_principal
46454687
then
4646-
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // calling to another subnet
4647-
let (Response', Cycles', S') = composite_query_helper(S, Cycles, Depth + 1, Root_canister_id, Canister_id, "", "", Call.callee, Call.method_name, Call.arg)
4648-
Cycles := Cycles'
4649-
S := S'
4688+
let (Response', Cycles_used') = management_canister_query(S, Canister_id, Call.method_name, Call.arg)
4689+
Cycles := Cycles - Cycles_used'
4690+
else
4691+
if S.canister_subnet[Canister_id].subnet_id ≠ S.canister_subnet[Call.callee].subnet_id
4692+
then
4693+
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // calling to another subnet
4694+
let (Response', Cycles', S') = composite_query_helper(S, Cycles, Depth + 1, Root_canister_id, Canister_id, "", "", Call.callee, Call.method_name, Call.arg)
4695+
Cycles := Cycles'
4696+
S := S'
46504697
if Cycles < MAX_CYCLES_PER_RESPONSE
46514698
then
46524699
Return (Reject (CANISTER_ERROR, <implementation-specific>), Cycles, S) // composite query out of cycles

docs/references/ic-interface-spec/changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ sidebar:
1414
(the time at which the canister's code was most recently deployed or a snapshot was loaded onto it),
1515
both expressed in nanoseconds since 1970-01-01. Both can be requested via `read_state` if
1616
`<canister_id>` matches the effective canister id of the request.
17+
* Composite query methods and their callbacks can call the management canister query methods
18+
`canister_status`, `canister_metrics`, `fetch_canister_logs`, and `list_canisters`.
19+
Such a call is always executed against the state of the subnet hosting the calling canister
20+
and it is subject to the same access control as the corresponding query call submitted by a user,
21+
with the calling canister as the caller. Calls to all other management canister methods are rejected.
1722

1823
### 0.65.0 (2026-08-03) {$0_65_0}
1924
* New canister setting `status_visibility` controlling who can read a canister's status via the

docs/references/ic-interface-spec/management-canister.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ The optional `sender_canister_version` parameter can contain the caller's canist
247247
### IC method `canister_status` {#ic-canister_status}
248248

249249
This method can be called by canisters as well as by external users via ingress messages.
250-
This method can also be called by external users via non-replicated (query) calls, but it cannot be called from composite query calls.
250+
This method can also be called via non-replicated (query) calls: by external users directly and by canisters from composite query methods and their callbacks.
251+
A call from a composite query is executed against the state of the subnet hosting the calling canister and can thus only target canisters hosted by that subnet.
251252

252253
Indicates various information about the canister. It contains:
253254

@@ -336,7 +337,8 @@ All sizes are expressed in bytes.
336337
### IC method `canister_metrics` {#ic-canister_metrics}
337338

338339
This method can be called by canisters as well as by external users via ingress messages.
339-
This method can also be called by external users via non-replicated (query) calls, but it cannot be called from composite query calls.
340+
This method can also be called via non-replicated (query) calls: by external users directly and by canisters from composite query methods and their callbacks.
341+
A call from a composite query is executed against the state of the subnet hosting the calling canister and can thus only target canisters hosted by that subnet.
340342

341343
This method returns a set of canister related metrics for the requested canister, like cycles consumed by different use cases. These metrics should be counters (i.e. monotonically increasing values) that report the accumulated respective amount since the canister was created for new canisters or since the metrics introduction for existing canisters.
342344

@@ -948,7 +950,8 @@ A snapshot may be deleted only by the controllers of the canister that the snaps
948950

949951
### IC method `fetch_canister_logs` {#ic-fetch_canister_logs}
950952

951-
This method can only be called by external users via non-replicated (query) calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls.
953+
This method can only be called via non-replicated (query) calls: by external users directly and by canisters from composite query methods and their callbacks, i.e., it cannot be called via replicated calls.
954+
A call from a composite query is executed against the state of the subnet hosting the calling canister and can thus only target canisters hosted by that subnet.
952955

953956
Given a canister ID as input, this method returns a vector of logs of that canister including its trap messages.
954957
The canister logs are *not* collected in canister methods running in non-replicated mode (NRQ, TQ, CQ, CRy, CRt, CC, and F modes, as defined in [Overview of imports](./canister-interface.md#system-api-imports)) and the canister logs are *purged* when the canister is reinstalled or uninstalled.
@@ -977,7 +980,8 @@ Replica-signed queries may improve security because the recipient can verify the
977980

978981
### IC method `list_canisters` {#ic-list_canisters}
979982

980-
This method can only be called by external users with subnet admin privileges via non-replicated (query) calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls.
983+
This method can only be called by subnet admins via non-replicated (query) calls: by external users directly and by canisters from composite query methods and their callbacks, i.e., it cannot be called via replicated calls.
984+
A call from a composite query returns the canisters on the subnet hosting the calling canister.
981985

982986
This method returns the list of all canisters on the subnet as consecutive canister ID ranges. Deleted canisters are not included in the result.
983987

docs/references/management-canister.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sidebar:
77

88
The management canister provides access to system features on the Internet Computer: creating and managing canisters, chain-key signing, HTTPS outcalls, randomness, and Bitcoin integration. It is not a real canister with its own state or Wasm module. It is a virtual canister implemented as part of the IC protocol itself.
99

10-
The management canister address is `aaaaa-aa` (the empty blob). It is present on every subnet. When you call `aaaaa-aa`, the IC routes the request to the appropriate subnet transparently.
10+
The management canister address is `aaaaa-aa` (the empty blob). It is present on every subnet. When you call `aaaaa-aa`, the IC routes the request to the appropriate subnet transparently. Calls made from a composite query are the exception: they are always answered by the calling canister's own subnet.
1111

1212
Most methods require the caller to be a **controller** of the target canister. Some methods (such as `raw_rand` and `deposit_cycles`) can only be called by canisters, not by external users. When an external user calls the management canister, the cost is charged to the managed canister.
1313

@@ -119,7 +119,7 @@ Removes a canister's code and state, making it empty. Outstanding calls are reje
119119

120120
Returns detailed information about a canister: status, settings, module hash, cycle balance, memory usage, and query statistics.
121121

122-
- **Caller:** Governed by the `status_visibility` setting (see below); the canister itself and subnet admins can always call it (canisters or external users; also available as a query call)
122+
- **Caller:** Governed by the `status_visibility` setting (see below); the canister itself and subnet admins can always call it (canisters or external users; also available as a query call, including from composite queries)
123123
- **Parameters:**
124124
- `canister_id` (`principal`)
125125
- **Returns:** A record containing:
@@ -141,7 +141,7 @@ By default, only controllers can read a canister's status. The `status_visibilit
141141

142142
Returns cycle consumption metrics for a canister broken down by use case. Metrics are monotonically increasing counters accumulating since canister creation (or since the metrics feature was introduced for existing canisters).
143143

144-
- **Caller:** Controllers or subnet admins (canisters or external users; also available as a query call, but query responses come from a single replica and are not suitable for security-sensitive use)
144+
- **Caller:** Controllers or subnet admins (canisters or external users; also available as a query call, including from composite queries, but query responses come from a single replica and are not suitable for security-sensitive use)
145145
- **Parameters:**
146146
- `canister_id` (`principal`)
147147
- **Returns:** A record containing:
@@ -536,7 +536,7 @@ For Bitcoin integration patterns, see the [Bitcoin guide](../guides/chain-fusion
536536

537537
Returns the most recent log entries for a canister. Logs are produced by `ic0.debug_print` and trap messages. Logs persist across upgrades but are purged on reinstall or uninstall. Total log size is capped at 4 KiB.
538538

539-
- **Caller:** External users only (query call; not callable by canisters)
539+
- **Caller:** External users via query calls, or canisters from composite queries (not callable via replicated calls)
540540
- **Parameters:** `canister_id` (`principal`)
541541
- **Returns:**
542542
- `canister_log_records` (`vec record { idx : nat64; timestamp_nanos : nat64; content : blob }`)
@@ -575,9 +575,9 @@ Returns metadata about a subnet.
575575

576576
### `list_canisters`
577577

578-
Returns all canisters hosted on the caller's subnet as a list of consecutive canister ID ranges. Deleted canisters are not included. Only callable by subnet admins as a query call; not callable by canisters, via replicated calls, or from composite query calls.
578+
Returns all canisters hosted on the caller's subnet as a list of consecutive canister ID ranges. Deleted canisters are not included. Only callable by subnet admins as a query call, either by an external user directly or by a canister from a composite query; not callable via replicated calls.
579579

580-
- **Caller:** Subnet admins only (query call; not callable by canisters)
580+
- **Caller:** Subnet admins only (query call, including from composite queries)
581581
- **Parameters:** none
582582
- **Returns:**
583583
- `canisters` (`vec record { start : principal; end : principal }`): contiguous ranges of canister IDs where `start` and `end` are both inclusive

0 commit comments

Comments
 (0)