From 838302aafff5b485c5f1c4f7e912481a8e8f164a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 18:07:28 +0200 Subject: [PATCH 1/2] infra: bump portal submodule to 9d734b5 Covers 3 new commits: effective-subnet-id HTTP endpoints (9d734b5), cloud_engine canister signature restriction (7581650), and a build fix (3b53578). Portal changes applied to split spec files: - index.md: cloud_engine subnet type must not appear in canister signatures - https-interface.md: new /api/v4/subnet//call and /api/v3/subnet//query endpoints; verify_response refactored to extract verify_node_signatures; new verify_subnet_response; new "Effective subnet id" section - abstract-behavior.md: subnet_admins field added to state; is_effective_subnet_id rules; new subnet call submission block; validate_sender_canister_version uses method_name; list_canisters updated for subnet queries; read_state conditions updated; may_read_path_for_subnet extended with request_status paths - changelog.md: v0.60.0 entry added --- .sources/portal | 2 +- .../ic-interface-spec/abstract-behavior.md | 116 +++++++++++++++--- .../references/ic-interface-spec/changelog.md | 6 + .../ic-interface-spec/https-interface.md | 53 +++++--- docs/references/ic-interface-spec/index.md | 2 +- 5 files changed, 148 insertions(+), 31 deletions(-) diff --git a/.sources/portal b/.sources/portal index d1da46d..9d734b5 160000 --- a/.sources/portal +++ b/.sources/portal @@ -1 +1 @@ -Subproject commit d1da46dd64fa89dcd191b0b9d901f84b3b2a615a +Subproject commit 9d734b55c9c3e0312282c929518dd756ae35c30f diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index 4d8867e..f7393e0 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -496,6 +496,7 @@ S = { canisters : CanisterId ↦ CanState; snapshots: CanisterId ↦ SnapshotId ↦ Snapshot; controllers : CanisterId ↦ Set Principal; + subnet_admins : SubnetId ↦ Set Principal; compute_allocation : CanisterId ↦ Nat; memory_allocation : CanisterId ↦ Nat; freezing_threshold : CanisterId ↦ Nat; @@ -713,14 +714,23 @@ delegation_targets(D) A `Request` has an effective canister id according to the rules in [Effective canister id](./https-interface.md#http-effective-canister-id): ``` -is_effective_canister_id(Request {canister_id = ic_principal, method = create_canister, …}, p) -is_effective_canister_id(Request {canister_id = ic_principal, method = provisional_create_canister_with_cycles, …}, p) -is_effective_canister_id(CanisterQuery {canister_id = ic_principal, method = list_canisters, …}, p) -is_effective_canister_id(Request {canister_id = ic_principal, method = install_chunked_code, arg = candid({target_canister = p, …}), …}, p) +is_effective_canister_id(Request {canister_id = ic_principal, method_name = "create_canister", …}, p) +is_effective_canister_id(Request {canister_id = ic_principal, method_name = "provisional_create_canister_with_cycles", …}, p) +is_effective_canister_id(CanisterQuery {canister_id = ic_principal, method_name = "list_canisters", …}, p) +is_effective_canister_id(Request {canister_id = ic_principal, method_name = "install_chunked_code", arg = candid({target_canister = p, …}), …}, p) is_effective_canister_id(Request {canister_id = ic_principal, arg = candid({canister_id = p, …}), …}, p) is_effective_canister_id(Request {canister_id = p, …}, p), if p ≠ ic_principal ``` +#### Effective subnet ids + +A `Request` has an effective subnet id according to the rules in [Effective subnet id](./https-interface.md#http-effective-subnet-id): +``` +is_effective_subnet_id(Request {canister_id = ic_principal, method_name = "create_canister", …}, s) +is_effective_subnet_id(Request {canister_id = ic_principal, method_name = "provisional_create_canister_with_cycles", …}, s) +is_effective_subnet_id(CanisterQuery {canister_id = ic_principal, method_name = "list_canisters", …}, s) +``` + #### API Request submission {#api-request-submission} After a replica (i.e., a node belonging to an IC subnet) receives a call in an HTTP request to `/api/v2/canister//call` or `/api/v4/canister//call` @@ -842,6 +852,46 @@ This is not instantaneous (the IC takes some time to agree it accepts the reques ::: +Submitted request to `/api//subnet//call` + +```html + +E : Envelope + +``` + +where `` is `v4`. + +Conditions + +```html + +E.content.canister_id ∈ verify_envelope(E, E.content.sender, S.system_time) +if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: + if not (E.content.sender_info = null): + verify_signature E.sender_pubkey E.content.sender_info.sig ("\x0Eic-sender-info" · E.content.sender_info.info) + E.content.sender_info.signer = Signing_canister_id +else: + E.content.sender_info = null +|E.content.nonce| <= 32 +E.content ∉ dom(S.requests) +S.system_time <= E.content.ingress_expiry +is_effective_subnet_id(E.content, ESID) +E.content.sender ∈ S.subnet_admins[ESID] +E.content.canister_id = ic_principal +E.content.method_name = "create_canister" ∨ E.content.method_name = "provisional_create_canister_with_cycles" + +``` + +State after + +```html + +S with + requests[E.content] = (Received, ESID) + +``` + #### Request rejection The IC may reject a received message for internal reasons (high load, low resources) or expiry. The precise conditions are not specified here, but the reject code must indicate this to be a system error. @@ -1390,7 +1440,7 @@ Note that returning does *not* imply that the call associated with this message The function `validate_sender_canister_version` checks that `sender_canister_version` matches the actual canister version of the sender in all calls to the methods of the management canister that take `sender_canister_version`: ``` validate_sender_canister_version(new_calls, canister_version_from_system) = - ∀ call ∈ new_calls. (call.callee = ic_principal and (call.method = 'create_canister' or call.method = 'update_settings' or call.method = 'install_code' or call.method = `install_chunked_code` or call.method = 'uninstall_code' or call.method = 'provisional_create_canister_with_cycles') and call.arg = candid(A) and A.sender_canister_version = n) => n = canister_version_from_system + ∀ call ∈ new_calls. (call.callee = ic_principal and (call.method_name = "create_canister" or call.method_name = "update_settings" or call.method_name = "install_code" or call.method_name = "install_chunked_code" or call.method_name = "uninstall_code" or call.method_name = "provisional_create_canister_with_cycles") and call.arg = candid(A) and A.sender_canister_version = n) => n = canister_version_from_system ``` The functions `query_as_update` and `system_task_as_update` turns a query function (note that composite query methods cannot be called when executing a message during this transition) resp the heartbeat or global timer into an update function; this is merely a notational trick to simplify the rule: @@ -4271,14 +4321,14 @@ verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // #### IC Management Canister: List canisters (query call) {#ic-mgmt-canister-list-canisters} This section specifies the `list_canisters` management canister query call. -It is a call to `/api/v3/canister//query` +It is a call to `/api/v3/canister//query` or `/api/v3/subnet//query` with CBOR content `Q` such that `Q.canister_id = ic_principal`. The management canister offers the method `list_canisters` that can be called as a query call by subnet admins and returns the list of all canisters on the subnet as consecutive canister ID ranges. -Submitted request to `/api/v3/canister//query` +Submitted request to `/api/v3/canister//query` or `/api/v3/subnet//query` ```html @@ -4294,13 +4344,31 @@ E.content = CanisterQuery Q Q.canister_id = ic_principal Q.method_name = 'list_canisters' |Q.nonce| <= 32 -is_effective_canister_id(E.content, ECID) S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id -verify_envelope(E, Q.sender, S.system_time) +Q.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) + +``` + +and + +```html + +is_effective_canister_id(E.content, ECID) Q.sender ∈ S.subnet_admins[S.canister_subnet[ECID]] ``` +for calls to `/api/v3/canister//query` and + +```html + +is_effective_subnet_id(E.content, ESID) +Q.sender ∈ S.subnet_admins[ESID] + +``` + +for calls to `/api/v3/subnet//query`. + Query response `R`: ```html @@ -4309,7 +4377,7 @@ Query response `R`: ``` -where `CanisterIdRanges` is the list of all canister IDs on the subnet encoded as consecutive canister ID ranges (excluding deleted canisters), and the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: +where `CanisterIdRanges` is the list of all canister IDs on the subnet encoded as consecutive canister ID ranges (excluding deleted canisters), and the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` or `/api/v3/subnet//read_state` satisfy the following: ```html @@ -4317,6 +4385,16 @@ verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // ``` +for calls to `/api/v3/canister//read_state` and + +```html + +verify_subnet_response(Q, R, Cert, ESID) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" + +``` + +for calls to `/api/v3/subnet//read_state`. + #### Query call {#query-call} 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). @@ -4496,13 +4574,13 @@ S' with :::note -Requesting paths with the prefix `/subnet` at `/api/v3/canister//read_state` might be deprecated in the future. Hence, users might want to point their requests for paths with the prefix `/subnet` to `/api/v3/subnet//read_state`. +Requesting paths with the prefix `/subnet` at `/api/v3/canister//read_state` might be deprecated in the future. Hence, users might want to point their requests for paths with the prefix `/subnet` to `/api/v3/subnet//read_state`. On the IC mainnet, the root subnet ID `tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe` can be used to retrieve the list of all IC mainnet's subnets by requesting the prefix `/subnet` at `/api/v3/subnet/tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe/read_state`. ::: -The user can read elements of the *state tree*, using a `read_state` request to `/api/v3/canister//read_state` or `/api/v3/subnet//read_state`. +The user can read elements of the *state tree*, using a `read_state` request to `/api/v3/canister//read_state` or `/api/v3/subnet//read_state`. Submitted request to `/api/v3/canister//read_state` @@ -4562,7 +4640,7 @@ may_read_path_for_canister(S, _, _) = False where `UTF8(name)` holds if `name` is encoded in UTF-8. -Submitted request to `/api/v3/subnet//read_state` +Submitted request to `/api/v3/subnet//read_state` ```html @@ -4575,10 +4653,11 @@ Conditions ```html E.content = ReadState RS -verify_envelope(E, RS.sender, S.system_time) +TS = verify_envelope(E, RS.sender, S.system_time) |E.content.nonce| <= 32 S.system_time <= RS.ingress_expiry ∀ path ∈ RS.paths. may_read_path_for_subnet(S, RS.sender, path) +∀ (["request_status", Rid] · _) ∈ RS.paths. ∀ R ∈ dom(S.requests). hash_of_map(R) = Rid => R.canister_id ∈ TS ``` @@ -4597,10 +4676,17 @@ may_read_path_for_subnet(S, _, ["subnet", sid]) = True may_read_path_for_subnet(S, _, ["subnet", sid, "public_key"]) = True may_read_path_for_subnet(S, _, ["subnet", sid, "type"]) = True may_read_path_for_subnet(S, _, ["subnet", sid, "canister_ranges"]) = sid == root_subnet_id -may_read_path_for_subnet(S, _, ["subnet", sid, "metrics"]) = sid == +may_read_path_for_subnet(S, _, ["subnet", sid, "metrics"]) = sid == ESID may_read_path_for_subnet(S, _, ["subnet", sid, "node"]) = True may_read_path_for_subnet(S, _, ["subnet", sid, "node", nid]) = True may_read_path_for_subnet(S, _, ["subnet", sid, "node", nid, "public_key"]) = True +may_read_path_for_subnet(S, _, ["request_status", Rid]) = +may_read_path_for_subnet(S, _, ["request_status", Rid, "status"]) = +may_read_path_for_subnet(S, _, ["request_status", Rid, "reply"]) = +may_read_path_for_subnet(S, _, ["request_status", Rid, "reject_code"]) = +may_read_path_for_subnet(S, _, ["request_status", Rid, "reject_message"]) = +may_read_path_for_subnet(S, _, ["request_status", Rid, "error_code"]) = + ∀ (R ↦ (_, ESID')) ∈ S.requests. hash_of_map(R) = Rid => RS.sender == R.sender ∧ ESID == ESID' may_read_path_for_subnet(S, _, _) = False ``` The response is a certificate `cert`, as specified in [Certification](./certification.md#certification), which passes `verify_cert` (assuming `S.root_key` as the root of trust), and where for every `path` documented in [The system state tree](./index.md#state-tree) that has a path in `RS.paths` or `["time"]` as a prefix, we have diff --git a/docs/references/ic-interface-spec/changelog.md b/docs/references/ic-interface-spec/changelog.md index 8dbfbf1..284abb9 100644 --- a/docs/references/ic-interface-spec/changelog.md +++ b/docs/references/ic-interface-spec/changelog.md @@ -8,6 +8,12 @@ sidebar: ## Changelog {#changelog} +### 0.60.0 (2025-05-04) {$0_60_0} +* Canister signatures from canisters on subnets of type `cloud_engine` are not valid. +* New HTTP endpoints for update calls (to create a canister by subnet admins) and + query calls (to list all canisters on a subnet by subnet admins) whose URL contains + the (effective) subnet ID on which the canister is created or whose canisters should be enumerated. + ### 0.59.0 (2025-04-27) {$0_59_0} * Update and query calls by users authenticated via canister signatures can carry auxiliary information signed by a canister signature issued by the same canister. diff --git a/docs/references/ic-interface-spec/https-interface.md b/docs/references/ic-interface-spec/https-interface.md index c10f34c..eddb2a1 100644 --- a/docs/references/ic-interface-spec/https-interface.md +++ b/docs/references/ic-interface-spec/https-interface.md @@ -12,15 +12,15 @@ The concrete mechanism that users use to send requests to the Internet Computer - At `/api/v2/canister//call`, the user can submit update calls that are asynchronous and might change the IC state. -- At `/api/v3/canister//call` (deprecated) and `/api/v4/canister//call`, the user can submit update calls and get a synchronous HTTPS response with a certificate for the call status. +- At `/api/v3/canister//call` (deprecated) and `/api/v4/canister//call`, the user can submit update calls and get a synchronous HTTPS response with a certificate for the call status. At `/api/v4/subnet//call`, the user can only submit the restricted subnet-scoped update call described by the [effective subnet id](#http-effective-subnet-id) rules. - At `/api/v2/canister//read_state` (deprecated), `/api/v2/subnet//read_state` (deprecated), `/api/v3/canister//read_state`, and `/api/v3/subnet//read_state`, the user can read various information about the state of the Internet Computer. In particular, they can poll for the status of a call here. -- At `/api/v2/canister//query` (deprecated) and `/api/v3/canister//query`, the user can perform (synchronous, non-state-changing) query calls. +- At `/api/v2/canister//query` (deprecated) and `/api/v3/canister//query`, the user can perform (synchronous, non-state-changing) query calls. At `/api/v3/subnet//query`, the user can only perform the restricted subnet-scoped query call described by the [effective subnet id](#http-effective-subnet-id) rules. - At `/api/v2/status` the user can retrieve status information about the Internet Computer. -In these paths, the `` is the [textual representation](./index.md#textual-ids) of the [*effective* canister id](#http-effective-canister-id). +In these paths, the `` is the [textual representation](./index.md#textual-ids) of the [*effective* canister id](#http-effective-canister-id) and the `` is the [textual representation](./index.md#textual-ids) of the [*effective* subnet id](#http-effective-subnet-id). Requests to `/api/.../call`, `/api/.../read_state`, and `/api/.../query` are POST requests with a CBOR-encoded request body, which consists of a authentication envelope (as per [Authentication](#authentication)) and request-specific content as described below. @@ -125,7 +125,7 @@ If an implementation specific timeout for the request is reached while the repli ### Request: Call {#http-call} -In order to call a canister, the user makes a POST request to `/api/v3/canister//call` (deprecated) or `/api/v4/canister//call`. The request body consists of an authentication envelope with a `content` map with the following fields: +In order to call a canister, the user makes a POST request to `/api/v3/canister//call` (deprecated) or `/api/v4/canister//call`. The `/api/v4/subnet//call` form is not a general-purpose call endpoint; it is only supported for canister creation calls to the Management Canister (`aaaaa-aa`). The request body consists of an authentication envelope with a `content` map with the following fields: - `request_type` (`text`): Always `call` @@ -175,7 +175,9 @@ If the `certificate` includes a subnet delegation (see [Delegation](./certificat - for requests to `/api/v3/canister//call`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at the path of the form `/subnet//canister_ranges`, -- for requests to `/api/v4/canister//call`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at a path with prefix `/canister_ranges/`. +- for requests to `/api/v4/canister//call`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at a path with prefix `/canister_ranges/`, + +- for requests to `/api/v4/subnet//call`, the `` must match the delegation's subnet id. This request type can *also* be used to call a query method (but not a composite query method). A user may choose to go this way, instead of via the faster and cheaper [Request: Query call](#http-query) below, if they want to get a *certified* response. Note that the canister state will not be changed by sending a call request type for a query method (except for transient state such as cycle balance, canister logs, and canister version). @@ -306,7 +308,7 @@ All requested paths must have the following form: - the sender of the original request referenced by `` is the same as the sender of the read state request and - - the effective canister id of the original request referenced by `` matches ``. + - the effective canister id of the original request referenced by `` matches `` (for requests to `/api/v2/canister//read_state` and `/api/v3/canister//read_state`), or the effective subnet id of the original request referenced by `` matches `` (for requests to `/api/v2/subnet//read_state` and `/api/v3/subnet//read_state`). - `/canister//module_hash`. Can be requested if `` matches ``. @@ -350,7 +352,7 @@ The following limits apply to the evaluation of a query call: - The wall clock time spent on evaluation of a query call is at most `MAX_WALL_CLOCK_TIME_COMPOSITE_QUERY`. -In order to make a query call to a canister, the user makes a POST request to `/api/v2/canister//query` (deprecated) or `/api/v3/canister//query`. The request body consists of an authentication envelope with a `content` map with the following fields: +In order to make a query call to a canister, the user makes a POST request to `/api/v2/canister//query` (deprecated) or `/api/v3/canister//query`. The `/api/v3/subnet//query` form is not a general-purpose query endpoint; it is only supported for calls to the `list_canisters` method of the Management Canister (`aaaaa-aa`). The request body consists of an authentication envelope with a `content` map with the following fields: - `request_type` (`text`): Always `"query"`. @@ -411,15 +413,13 @@ A successful response to a query call (200 HTTP status) contains a list with one - `identity` (`principal`): the principal of the node producing the signature. -Given a query (the `content` map from the request body) `Q`, a response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state`, the following predicate describes when the returned response `R` is correctly signed: +Given a query (the `content` map from the request body) `Q`, a response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` (or `/api/v3/subnet//read_state` for subnet queries), the following predicates describe when the returned response `R` is correctly signed. + +The common node signature verification logic is captured in: ``` -verify_response(Q, R, Cert) - = verify_cert(Cert) ∧ - ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ - (Cert.delegation ≠ NoDelegation ∧ SubnetId = Cert.delegation.subnet_id ∧ lookup*(["canister_ranges",SubnetId], Cert.delegation.certificate) = Ranges)) ∧ - effective_canister_id ∈ Ranges ∧ - ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. +verify_node_signatures(Q, R, Cert, SubnetId) + = ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. lookup(["subnet",SubnetId,"node",NodeId,"public_key"], Cert) = Found PK ∧ if R.status = "replied" then verify_signature PK Sig ("\x0Bic-response" · hash_of_map({ @@ -437,6 +437,27 @@ verify_response(Q, R, Cert) request_id: hash_of_map(Q)})) ``` +For canister queries to `/api/v3/canister//query`: + +``` +verify_response(Q, R, Cert) + = verify_cert(Cert) ∧ + ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ + (Cert.delegation ≠ NoDelegation ∧ SubnetId = Cert.delegation.subnet_id ∧ lookup*(["canister_ranges",SubnetId], Cert.delegation.certificate) = Ranges)) ∧ + effective_canister_id ∈ Ranges ∧ + verify_node_signatures(Q, R, Cert, SubnetId) +``` + +For subnet queries to `/api/v3/subnet//query`: + +``` +verify_subnet_response(Q, R, Cert, SubnetId) + = verify_cert(Cert) ∧ SubnetId = effective_subnet_id ∧ + ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId) ∨ + (Cert.delegation ≠ NoDelegation ∧ SubnetId = Cert.delegation.subnet_id)) ∧ + verify_node_signatures(Q, R, Cert, SubnetId) +``` + where `RootSubnetId` is the a priori known principal of the root subnet. Moreover, all timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation must be "recent enough". :::note @@ -480,6 +501,10 @@ In development instances of the Internet Computer Protocol (e.g. testnets), the ::: +### Effective subnet id {#http-effective-subnet-id} + +The `` in the URL paths of update call requests is only supported for canister creation calls to the Management Canister (`aaaaa-aa`). In this case, the `` specifies the subnet on which the new canister will be created. The `` in the URL paths of query call requests is only supported for calls to the `list_canisters` method of the Management Canister (`aaaaa-aa`). In this case, the `` specifies the subnet whose canisters are listed. + ### Authentication {#authentication} All requests coming in via the HTTPS interface need to be either *anonymous* or *authenticated* using a cryptographic signature. To that end, the following fields are present in the `content` map in all cases: diff --git a/docs/references/ic-interface-spec/index.md b/docs/references/ic-interface-spec/index.md index 232b316..feaeaed 100644 --- a/docs/references/ic-interface-spec/index.md +++ b/docs/references/ic-interface-spec/index.md @@ -414,7 +414,7 @@ This section makes forward references to other concepts in this document, in par where `signing_canister_id` is the id of the signing canister and `reconstruct` is a function that computes a root-hash for the tree. - - If the `certificate` includes a subnet delegation, then the `signing_canister_id` must be included in the delegation's canister id range (see [Delegation](./certification.md#certification-delegation)). + - If the `certificate` includes a subnet delegation, then the `signing_canister_id` must be included in the delegation's canister id range (see [Delegation](./certification.md#certification-delegation)), and the subnet type (obtained by looking up the path `/subnet//type` in the delegation's certificate) *must* be present and it *must not* be `cloud_engine`. - The `tree` must be a `well_formed` tree with ``` From 0e2b11507d04d712818c23ea1f5358fef0397122 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 18:15:11 +0200 Subject: [PATCH 2/2] fix(spec): add /api/v4/subnet//call to API request submission paragraph One sentence was missed during the portal sync: the "API Request submission" intro paragraph in abstract-behavior.md did not include the new subnet-scoped call endpoint alongside the canister-scoped endpoints. --- docs/references/ic-interface-spec/abstract-behavior.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index f7393e0..820a44a 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -733,7 +733,7 @@ is_effective_subnet_id(CanisterQuery {canister_id = ic_principal, method_name = #### API Request submission {#api-request-submission} -After a replica (i.e., a node belonging to an IC subnet) receives a call in an HTTP request to `/api/v2/canister//call` or `/api/v4/canister//call` +After a replica (i.e., a node belonging to an IC subnet) receives a call in an HTTP request to `/api/v2/canister//call`, `/api/v4/canister//call`, or `/api/v4/subnet//call` and if the replica accepts the call and subsequently the IC subnet (as a whole) receives the call, then the call gets added to the IC state as `Received`. This can only happen if the target canister is not frozen and