From abd8312ea4bfcd33e713be9bf18a4a75f84ae0e0 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 13 May 2026 14:02:15 +0000 Subject: [PATCH 1/2] feat: flexible canister http outcalls --- .../ic-interface-spec/abstract-behavior.md | 9 +++ .../ic-interface-spec/canister-interface.md | 59 +++++++++++++++++- .../ic-interface-spec/management-canister.md | 62 +++++++++++++++++-- public/references/ic.did | 46 ++++++++++++++ 4 files changed, 168 insertions(+), 8 deletions(-) diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index fc268f0f..6ebfa55d 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -5486,6 +5486,11 @@ ic0.subnet_self_copy(dst : I, offset : I, size : I) = if es.context = s then Trap {cycles_used = es.cycles_used;} copy_to_canister(dst, offset, size, es.params.sysenv.subnet_id) +I ∈ {i32, i64} +ic0.subnet_self_node_count() : I = + if es.context = s then Trap {cycles_used = es.cycles_used;} + return es.params.sysenv.subnet_size + ic0.canister_cycle_balance() : i64 = if es.context = s then Trap {cycles_used = es.cycles_used;} if es.balance >= 2^64 then Trap {cycles_used = es.cycles_used;} @@ -5747,6 +5752,10 @@ I ∈ {i32, i64} ic0.cost_http_request(request_size: i64, max_res_bytes: i64, dst: I) : () = copy_cycles_to_canister(dst, arbitrary()) +I ∈ {i32, i64} +ic0.cost_http_request_v2(params_src : I, params_size : I, dst : I) : ()= + copy_cycles_to_canister(dst, arbitrary()) + I ∈ {i32, i64} ic0.cost_sign_with_ecdsa(src: I, size: I, ecdsa_curve: i32, dst: I) : i32 = known_keys = arbitrary() diff --git a/docs/references/ic-interface-spec/canister-interface.md b/docs/references/ic-interface-spec/canister-interface.md index 0db58772..519ad223 100644 --- a/docs/references/ic-interface-spec/canister-interface.md +++ b/docs/references/ic-interface-spec/canister-interface.md @@ -215,6 +215,12 @@ The 32-bit stable memory System API (`ic0.stable_size`, `ic0.stable_grow`, `ic0. ::: +:::note + +The `ic0.cost_http_request` System API call is DEPRECATED. Canister developers are advised to use the `ic0.cost_http_request_v2` call instead. + +::: + The following sections describe various System API functions, also referred to as system calls, which we summarize here. All the following functions belong to the `ic0` module (denoted by the prefix `ic0.`). @@ -259,6 +265,7 @@ defaulting to `I = i32` if the canister declares no memory. ic0.subnet_self_size : () -> I; // * ic0.subnet_self_copy : (dst : I, offset : I, size : I) -> (); // * + ic0.subnet_self_node_count : () -> i32; // * ic0.msg_method_name_size : () -> I; // F ic0.msg_method_name_copy : (dst : I, offset : I, size : I) -> (); // F @@ -301,6 +308,7 @@ defaulting to `I = i32` if the canister declares no memory. ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> (); // * s ic0.cost_create_canister : (dst : I) -> (); // * s ic0.cost_http_request : (request_size : i64, max_res_bytes : i64, dst : I) -> (); // * s + ic0.cost_http_request_v2 : (params_src : I, params_size : I, dst : I) -> (); // * s ic0.cost_sign_with_ecdsa : (src : I, size : I, ecdsa_curve: i32, dst : I) -> i32; // * s ic0.cost_sign_with_schnorr : (src : I, size : I, algorithm: i32, dst : I) -> i32; // * s ic0.cost_vetkd_derive_key : (src : I, size : I, vetkd_curve: i32, dst : I) -> i32; // * s @@ -503,9 +511,9 @@ A canister can learn about its own identity: A canister can learn about the subnet it is running on: -- `ic0.subnet_self_size : () → I` and `ic0.subnet_self_copy: (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` +- `ic0.subnet_self_size : () → I`, `ic0.subnet_self_copy: (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}`, and `ic0.subnet_self_node_count : () -> i32` - These functions allow the canister to query the subnet id (as a blob) of the subnet on which the canister is running. + These functions allow the canister to query the subnet id (as a blob) of the subnet on which the canister is running, and to retrieve the number of nodes that are currently on the subnet. ### Canister status {#system-api-canister-status} @@ -910,7 +918,13 @@ These system calls return costs in Cycles, represented by 128 bits, which will b - `ic0.cost_http_request(request_size : i64, max_res_bytes : i64, dst : I) -> ()`; `I ∈ {i32, i64}` - The cost of a canister http outcall via [`http_request`](./management-canister.md#ic-http_request). `request_size` is the sum of the byte lengths of the following components of an http request: + :::note + + The `ic0.cost_http_request` System API call is DEPRECATED. Canister developers are advised to use the `ic0.cost_http_request_v2` call instead. + + ::: + + The cost of a canister HTTP outcall via [`http_request`](./management-canister.md#ic-http_request) with the pricing version set to `1` (currently the default). `request_size` is the sum of the byte lengths of the following components of an http request: - url - headers - i.e., the sum of the lengths of all keys and values - body @@ -918,6 +932,45 @@ These system calls return costs in Cycles, represented by 128 bits, which will b `max_res_bytes` is the maximum response length the caller wishes to accept (the caller should provide the default value of `2,000,000` if no maximum response length is provided in the actual request to the management canister). +- `ic0.cost_http_request_v2(params_src: I, params_size: I, dst : I) -> (); I ∈ {i32, i64}` + + The cost of a canister HTTP outcall via [`http_request`](./management-canister.md#ic-http_request) with the pricing version set to `2`. The blob described by `params_src` and `params_size` must be a valid Candid encoding of a value of the following type: + ``` + record { + request_bytes : nat64; + http_roundtrip_time_ms : nat64; + raw_response_bytes : nat64; + transformed_response_bytes : nat64; + transform_instructions: nat64; + outcall_type : opt variant { + fully_replicated: reserved; + non_replicated: reserved; + flexible: opt record { + min_responses: nat32; + max_responses: nat32; + total_requests: nat32; + } + } + } + ``` + + The function traps if `params_src` and `params_size` do not describe a valid Candid encoding of a value of the above type, or if the encoding contains additional fields other than the ones above. The function returns the cycle cost of an HTTP outcall whose execution uses up exactly the amount of resources specified by the individual fields: + - `request_bytes` is the sum of the byte lengths of the following components of an HTTP request: + - `url` + - `headers` - i.e., the sum of the lengths of all keys and values + - `body` + - `transform` - i.e., the sum of the transform method name length and the length of the transform context. + + - `http_roundtrip_time_ms` is the amount of time between the time when the HTTP request starts being sent to the remote server and the time that the HTTP response is fully received (in milliseconds). + + - `raw_response_bytes` is the length of the HTTP response. + + - `transformed_response_bytes` is the length of the HTTP response after transformation. + + - `transform_instructions` is the number of instructions the transform function takes. + + - `outcall_type` is the type of HTTP outcall issued: a fully replicated call (made through the `http_request` endpoint with `is_replicated` set to `null` or `opt false`), non-replicated (made through `http_request` with `is_replicated` set to `opt true`), or flexible (made through the `flexible_http_request` endpoint). When the `flexible` outcall variant is selected, it can optionally be supplemented with the `min_responses`, `max_responses`, and `total_requests` parameters provided to the endpoint. + - `ic0.cost_sign_with_ecdsa(src : I, size : I, ecdsa_curve: i32, dst : I) -> i32`; `I ∈ {i32, i64}` - `ic0.cost_sign_with_schnorr(src : I, size : I, algorithm: i32, dst : I) -> i32`; `I ∈ {i32, i64}` diff --git a/docs/references/ic-interface-spec/management-canister.md b/docs/references/ic-interface-spec/management-canister.md index 861022c1..1ae8da4f 100644 --- a/docs/references/ic-interface-spec/management-canister.md +++ b/docs/references/ic-interface-spec/management-canister.md @@ -646,7 +646,7 @@ The following parameters should be supplied for the call: - `url` - the requested URL. The URL must be valid according to [RFC-3986](https://www.ietf.org/rfc/rfc3986.txt), it might contain non-ASCII characters according to [RFC-3987](https://www.ietf.org/rfc/rfc3987.txt), and its length must not exceed `8192`. The URL may specify a custom port number. -- `max_response_bytes` - optional, specifies the maximal size of the response in bytes. If provided, the value must not exceed `2MB` (`2,000,000B`). The call will be charged based on this parameter. If not provided, the maximum of `2MB` will be used. +- `max_response_bytes` - optional, specifies the maximal size of the response in bytes. If provided, the value must not exceed `2MB` (`2,000,000B`). If not provided, the maximum of `2MB` will be used. When the `pricing_version` is set to `1`, the call will be charged based on this parameter. When the `pricing_version` is set to `2`, this field is ignored. - `method` - currently, `GET`, `HEAD`, and `POST` are supported. Additionally, `PUT` and `DELETE` are supported in non-replicated mode only. @@ -658,13 +658,17 @@ The following parameters should be supplied for the call: - `is_replicated` - optional, selecting between replicated and non-replicated modes. -:::note + :::note -The `is_replicated` field is considered EXPERIMENTAL. + The `is_replicated` field is considered EXPERIMENTAL. -::: + ::: -Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). +- `pricing_version` - the version of the pricing mechanism for HTTP outcalls that should be applied to this call; it can be either `1` or `2`. For compatibility reasons, the default is `1`; however, version `1` is deprecated. + +Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). Extraneous cycles are refunded: +- with pricing version `1`, the difference between the attached cycles and the cost returned by the `ic0.cost_http_request` API with the appropriate parameters +- with pricing version `2`, any attached cycles exceeding those used by the outcall execution. The returned response (and the response provided to the `transform` function, if specified) contains the following fields: @@ -703,6 +707,54 @@ If you do not specify the `max_response_bytes` parameter, the maximum of a `2MB` ::: +### IC method `flexible_http_request` {#ic-flexible_http_request} + +This is a variant of the [`http_request`](#ic-http_request) method where nodes return their individual HTTP responses to the caller instead of trying to reach consensus on the response, letting the caller do its own HTTP response processing. Use cases include calling HTTP endpoints that provide rapidly changing information (where achieving consensus is unlikely) and letting the user pick a trade-off between cheaper calls (fewer replicas requesting/responding) and stronger integrity guarantees (more replicas requesting/responding). + +The arguments of the call are as for `http_request`, except that: + +- there is an additional optional argument `replication`. When set, the caller can specify how many nodes should issue an HTTP outcall, the minimum number of HTTP responses from nodes in order for the outcall to succeed (`min_responses`), and the maximum number of HTTP responses the caller is willing to receive as the result of the outcall (`max_responses`). That is, a successful HTTP outcall is guaranteed to return between `min_responses` and `max_responses`. If `replication` is set, then the caller must ensure that `0 <= min_responses <= max_responses <= total_requests` and `1 <= total_requests <= N`, where `N` is the number of the nodes on the caller's subnet, otherwise the call will fail. The caller may use the `ic0.subnet_self_node_count` System API call to determine `N`. If `replication` is not provided, the defaults of `floor(2 / 3 * N) + 1`, `N` and `N` are used for `min_responses`, `max_responses` and `total_requests`. + +- the deprecated `max_response_bytes` argument is not supported. + +The other arguments, `url`, `method`, `headers`, `body`, and `transform` are the same as for `http_request`. The result is a vector of responses, with each individual response having the same structure as a `http_request` response, providing `status`, `headers`, and `body` fields. + +As for `http_request`, the endpoint specified by the provided `url` should be idempotent. The one exception is when `total_requests` is set to 1 in `replication`. The request restrictions are also the same as for the `http_request` method: + +- The total number of bytes in the request must not exceed `2MB` (`2,000,000`) bytes. + +- Only the `GET`, `HEAD`, and `POST` methods are supported. + +- The number of headers must not exceed `64`. + +- The number of bytes representing a header name or value must not exceed `8KiB`. + +- The total number of bytes representing the header names and values must not exceed `48KiB`. + +The response from the remote server must not exceed `2MB`. Moreover, the total size of the result, that is, the sum of the responses returned by the different replicas (possibly after the transform function), must also not exceed 2MB. + +Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). The unused cycles are then refunded to the caller. + +The method may return an error of the `flexible_http_request_err` type. The error includes a textual error message, an optional global error code, and a vector of resource reports from individual nodes. + +The `global_error` field describes why the aggregate call failed to meet the requirements: + +- `timeout`, meaning that less than `min_responses` from the nodes have been collected before some system-defined timeout. + +- `out_of_cycles` indicating that the attached cycles were not enough to cover the processing of at least `min_responses`. + +- `responses_too_large`: indicating that no combination of at least `min_responses` available responses could fit into the 2MB total limit. + +- `too_many_rejects`: indicating that more than `total_requests - min_responses` nodes returned reject responses, so at least `min_responses` successful responses can never be collected. + +The `node_details` vector provides visibility into the execution on specific nodes. Each entry contains: + +- `node_id`. + +- `report`: A detailed accounting of resources (bytes, instructions, time, and cycles) used by the node. Note: If a node fails due to a resource limit or running out of cycles, the corresponding field in this report will be set to `exceeded` rather than `used`. + +- `error`: An optional record containing a `code` and `message`. This is populated only when the node encounters a functional failure. + ### IC method `node_metrics_history` {#ic-node_metrics_history} This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. diff --git a/public/references/ic.did b/public/references/ic.did index cad1d2b4..f1289edf 100644 --- a/public/references/ic.did +++ b/public/references/ic.did @@ -112,6 +112,34 @@ type http_request_result = record { body : blob; }; +type http_request_resource_report = record { + raw_response_bytes: opt variant { used: nat64; exceeded: reserved }; + http_roundtrip_time_ms: opt variant { used: nat64; exceeded: reserved }; + transform_instructions: opt variant { used: nat64; exceeded: reserved }; + transformed_response_bytes: opt variant { used: nat64; exceeded: reserved }; + cycles: opt variant { used: nat; exceeded: reserved }; +}; + +type flexible_http_request_err = record { + global_error: opt variant { + timeout : reserved; + out_of_cycles : reserved; + responses_too_large : reserved; + too_many_rejects : reserved; + }; + node_details : vec record { + node_id: principal; + report: http_request_resource_report; + error: opt record { code: text; message: text }; + }; + message: text; +}; + +type flexible_http_request_result = variant { + ok: vec http_request_result; + err: flexible_http_request_err; +}; + type ecdsa_curve = variant { secp256k1; }; @@ -352,6 +380,23 @@ type http_request_args = record { context : blob; }; is_replicated : opt bool; + pricing_version : opt nat32; +}; + +type flexible_http_request_args = record { + url : text; + method : variant { get; head; post }; + headers : vec http_header; + body : opt blob; + transform : opt record { + function : func(record { response : http_request_result; context : blob }) -> (http_request_result) query; + context : blob; + }; + replication: opt record { + min_responses: nat32; + max_responses: nat32; + total_requests: nat32; + }; }; type ecdsa_public_key_args = record { @@ -646,6 +691,7 @@ service ic : { deposit_cycles : (deposit_cycles_args) -> (); raw_rand : () -> (raw_rand_result); http_request : (http_request_args) -> (http_request_result); + flexible_http_request : (flexible_http_request_args) -> (flexible_http_request_result); // Public canister data canister_info : (canister_info_args) -> (canister_info_result); From 94b6292feaf3ecbe544328804aef452c30106ecc Mon Sep 17 00:00:00 2001 From: Leo Eichhorn Date: Tue, 1 Sep 2026 08:51:28 +0000 Subject: [PATCH 2/2] update --- .../ic-interface-spec/abstract-behavior.md | 6 ++- .../ic-interface-spec/canister-interface.md | 8 +-- .../ic-interface-spec/management-canister.md | 54 +++++++++++-------- public/references/ic.did | 5 +- 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index 6ebfa55d..009d0e87 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -5486,8 +5486,7 @@ ic0.subnet_self_copy(dst : I, offset : I, size : I) = if es.context = s then Trap {cycles_used = es.cycles_used;} copy_to_canister(dst, offset, size, es.params.sysenv.subnet_id) -I ∈ {i32, i64} -ic0.subnet_self_node_count() : I = +ic0.subnet_self_node_count() : i32 = if es.context = s then Trap {cycles_used = es.cycles_used;} return es.params.sysenv.subnet_size @@ -5754,6 +5753,9 @@ ic0.cost_http_request(request_size: i64, max_res_bytes: i64, dst: I) : () = I ∈ {i32, i64} ic0.cost_http_request_v2(params_src : I, params_size : I, dst : I) : ()= + params = copy_from_canister(params_src, params_size) + if params is not a valid Candid encoding of an HTTP outcall cost parameter record then + Trap {cycles_used = es.cycles_used;} copy_cycles_to_canister(dst, arbitrary()) I ∈ {i32, i64} diff --git a/docs/references/ic-interface-spec/canister-interface.md b/docs/references/ic-interface-spec/canister-interface.md index 519ad223..bce5a5d9 100644 --- a/docs/references/ic-interface-spec/canister-interface.md +++ b/docs/references/ic-interface-spec/canister-interface.md @@ -934,7 +934,7 @@ These system calls return costs in Cycles, represented by 128 bits, which will b - `ic0.cost_http_request_v2(params_src: I, params_size: I, dst : I) -> (); I ∈ {i32, i64}` - The cost of a canister HTTP outcall via [`http_request`](./management-canister.md#ic-http_request) with the pricing version set to `2`. The blob described by `params_src` and `params_size` must be a valid Candid encoding of a value of the following type: + The cost of a canister HTTP outcall, either via [`http_request`](./management-canister.md#ic-http_request) with the pricing version set to `2`, or via [`flexible_http_request`](./management-canister.md#ic-flexible_http_request), which takes no pricing version argument and is priced this way. The blob described by `params_src` and `params_size` must be a valid Candid encoding of a value of the following type: ``` record { request_bytes : nat64; @@ -954,7 +954,9 @@ These system calls return costs in Cycles, represented by 128 bits, which will b } ``` - The function traps if `params_src` and `params_size` do not describe a valid Candid encoding of a value of the above type, or if the encoding contains additional fields other than the ones above. The function returns the cycle cost of an HTTP outcall whose execution uses up exactly the amount of resources specified by the individual fields: + The function traps if `params_src` and `params_size` do not describe a valid Candid encoding of a value of the above type. Beyond that type, decoding may skip only a very small, fixed amount of data, so the payload of the `fully_replicated` and `non_replicated` variants must be encoded as `null` and the encoding must not carry record fields other than the ones above; an encoding that violates either of these may trap. Similarly, the function also traps if the given blob is too large. + + The function returns the amount of cycles to attach to an HTTP outcall in which every participating node consumes exactly the amount of resources specified by the individual fields. Part of this amount is a _reservation_ rather than a charge: every node the outcall is assigned to is assumed to attempt it, and enough is reserved to fund whichever result ends up being delivered, including a reject delivered in place of the response that was asked for. Whatever is not spent is refunded (see [`http_request`](./management-canister.md#ic-http_request)), so the actual cost of such an outcall may be less than this system call predicts, but assuming parameters are accurate, it cannot be more. The individual fields are: - `request_bytes` is the sum of the byte lengths of the following components of an HTTP request: - `url` - `headers` - i.e., the sum of the lengths of all keys and values @@ -969,7 +971,7 @@ These system calls return costs in Cycles, represented by 128 bits, which will b - `transform_instructions` is the number of instructions the transform function takes. - - `outcall_type` is the type of HTTP outcall issued: a fully replicated call (made through the `http_request` endpoint with `is_replicated` set to `null` or `opt false`), non-replicated (made through `http_request` with `is_replicated` set to `opt true`), or flexible (made through the `flexible_http_request` endpoint). When the `flexible` outcall variant is selected, it can optionally be supplemented with the `min_responses`, `max_responses`, and `total_requests` parameters provided to the endpoint. + - `outcall_type` is the type of HTTP outcall issued: a fully replicated call (made through the `http_request` endpoint with `is_replicated` set to `null` or `opt true`), non-replicated (made through `http_request` with `is_replicated` set to `opt false`), or flexible (made through the [`flexible_http_request`](./management-canister.md#ic-flexible_http_request) endpoint). If `outcall_type` is absent, the cost of a fully replicated call is returned. When the `flexible` outcall variant is selected, it can optionally be supplemented with the `min_responses`, `max_responses`, and `total_requests` parameters provided to the endpoint; if that record is omitted, the endpoint's own defaults of `floor(2 / 3 * N) + 1`, `N` and `N` are used, where `N` is the number of the nodes on the caller's subnet. Unlike the endpoint, this System API call does not validate the counts: a combination that `flexible_http_request` would reject simply yields a price that no outcall will ever be charged. - `ic0.cost_sign_with_ecdsa(src : I, size : I, ecdsa_curve: i32, dst : I) -> i32`; `I ∈ {i32, i64}` diff --git a/docs/references/ic-interface-spec/management-canister.md b/docs/references/ic-interface-spec/management-canister.md index 1ae8da4f..8749c511 100644 --- a/docs/references/ic-interface-spec/management-canister.md +++ b/docs/references/ic-interface-spec/management-canister.md @@ -624,7 +624,7 @@ In the replicated mode, the responses for all identical requests must match, too For this reason, the calling canister can supply a transformation function, which the IC uses to let the canister sanitize the responses from such unique values. The transformation function is executed separately on the corresponding response received for a request (both in replicated and non-replicated modes). Only the transformed response will be available to the calling canister. -Currently, the `GET`, `HEAD`, and `POST` methods are supported for HTTP requests. Additionally, the `PUT` and `DELETE` methods are supported in non-replicated mode only. `PUT` and `DELETE` are restricted to non-replicated mode to avoid confusing race conditions that may occur with replicated execution. +Currently, the `GET`, `HEAD`, and `POST` methods are supported for HTTP requests. Additionally, the `PUT`, `DELETE`, and `PATCH` methods are supported in non-replicated mode only. `PUT`, `DELETE`, and `PATCH` are restricted to non-replicated mode to avoid confusing race conditions that may occur with replicated execution. It is important to note the following for the usage of the `POST` method: @@ -646,9 +646,9 @@ The following parameters should be supplied for the call: - `url` - the requested URL. The URL must be valid according to [RFC-3986](https://www.ietf.org/rfc/rfc3986.txt), it might contain non-ASCII characters according to [RFC-3987](https://www.ietf.org/rfc/rfc3987.txt), and its length must not exceed `8192`. The URL may specify a custom port number. -- `max_response_bytes` - optional, specifies the maximal size of the response in bytes. If provided, the value must not exceed `2MB` (`2,000,000B`). If not provided, the maximum of `2MB` will be used. When the `pricing_version` is set to `1`, the call will be charged based on this parameter. When the `pricing_version` is set to `2`, this field is ignored. +- `max_response_bytes` - optional, specifies the maximal size of the response in bytes. If provided, the value must not exceed `2MB` (`2,000,000B`). If not provided, the maximum of `2MB` will be used. The limit applies for both pricing versions, and is enforced on the response received from the remote server as well as on the response produced by the `transform` function. Only pricing version `1` ("legacy") also charges the call upfront based on this parameter; with pricing version `2` ("pay-as-you-go"), the call is charged only for the resources it actually consumes. -- `method` - currently, `GET`, `HEAD`, and `POST` are supported. Additionally, `PUT` and `DELETE` are supported in non-replicated mode only. +- `method` - currently, `GET`, `HEAD`, and `POST` are supported. Additionally, `PUT`, `DELETE`, and `PATCH` are supported in non-replicated mode only. - `headers` - list of HTTP request headers and their corresponding values @@ -656,7 +656,7 @@ The following parameters should be supplied for the call: - `transform` - an optional record that includes a function that transforms raw responses to sanitized responses, and a byte-encoded context that is provided to the function upon invocation, along with the response to be sanitized. If provided, the calling canister itself must export this function -- `is_replicated` - optional, selecting between replicated and non-replicated modes. +- `is_replicated` - optional, selecting between replicated and non-replicated modes. Setting the field to `opt false` selects the non-replicated mode, in which a single node chosen by the system performs the request. Setting it to `opt true`, or omitting it, selects the replicated mode. :::note @@ -664,11 +664,15 @@ The following parameters should be supplied for the call: ::: -- `pricing_version` - the version of the pricing mechanism for HTTP outcalls that should be applied to this call; it can be either `1` or `2`. For compatibility reasons, the default is `1`; however, version `1` is deprecated. +- `pricing_version` - optional, the version of the pricing mechanism for HTTP outcalls that should be applied to this call; it can be either `1` ("legacy") or `2` ("pay-as-you-go"). For compatibility reasons, the default is `1`; however, version `1` is deprecated. If the field is omitted, set to a version the subnet does not support, or set to any other value, the call is priced with version `1` and no error is reported. Note that pricing version `1` does not take the replication mode into account, so a non-replicated call is charged the same as a replicated one with the same request size and `max_response_bytes`; only version `2` prices a call according to its replication mode. -Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). Extraneous cycles are refunded: -- with pricing version `1`, the difference between the attached cycles and the cost returned by the `ic0.cost_http_request` API with the appropriate parameters -- with pricing version `2`, any attached cycles exceeding those used by the outcall execution. +Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). How many cycles must be attached, and what is refunded, depends on the pricing version: + +- with pricing version `1`, the call is rejected unless the attached cycles cover the cost returned by the `ic0.cost_http_request` API with the appropriate parameters; the difference between the attached cycles and that cost is refunded. + +- with pricing version `2`, the call is rejected unless the attached cycles cover a base fee that depends on the request and is charged when the call is accepted. Any attached cycles exceeding those used by the outcall execution are refunded. + +The cycles attached beyond the base fee of a pricing version `2` call are not merely a payment: They are withheld and split evenly into a budget for each node performing the outcall, and each node's remaining budget bounds the response it may download, the time it may wait for it, and the number of instructions its execution of the `transform` function may use. A call that covers the base fee but is funded below the amount reported by the `ic0.cost_http_request_v2` API for the resources it will use, is therefore not rejected up front: it runs with reduced limits, and a node that exhausts its budget produces a `CANISTER_REJECT` response instead of the response it was asked for. If what the nodes leave unspent no longer covers delivering any response at all, the call is answered with a `SYS_TRANSIENT` reject; since the cost of delivering a response depends on its size, this can happen after the remote server has already been contacted. The unspent part of the per-node budgets is credited to the caller's cycles balance asynchronously, separately from the refund that accompanies the response. The returned response (and the response provided to the `transform` function, if specified) contains the following fields: @@ -703,27 +707,33 @@ The Internet Computer mainnet supports requests to both IPv6 and IPv4 destinatio :::warning -If you do not specify the `max_response_bytes` parameter, the maximum of a `2MB` response will be charged for, which is expensive in terms of cycles. Always set the parameter to a reasonable upper bound of the expected (network and transformed) response size to not incur unnecessary cycles costs for your request. +With pricing version `1`, if you do not specify the `max_response_bytes` parameter, the maximum of a `2MB` response will be charged for, which is expensive in terms of cycles. Always set the parameter to a reasonable upper bound of the expected (network and transformed) response size to not incur unnecessary cycles costs for your request. ::: ### IC method `flexible_http_request` {#ic-flexible_http_request} +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + This is a variant of the [`http_request`](#ic-http_request) method where nodes return their individual HTTP responses to the caller instead of trying to reach consensus on the response, letting the caller do its own HTTP response processing. Use cases include calling HTTP endpoints that provide rapidly changing information (where achieving consensus is unlikely) and letting the user pick a trade-off between cheaper calls (fewer replicas requesting/responding) and stronger integrity guarantees (more replicas requesting/responding). +Flexible outcalls have no `pricing_version` argument; on subnets that charge for HTTP outcalls they are always priced with pricing version `2` ("pay-as-you-go"). + The arguments of the call are as for `http_request`, except that: -- there is an additional optional argument `replication`. When set, the caller can specify how many nodes should issue an HTTP outcall, the minimum number of HTTP responses from nodes in order for the outcall to succeed (`min_responses`), and the maximum number of HTTP responses the caller is willing to receive as the result of the outcall (`max_responses`). That is, a successful HTTP outcall is guaranteed to return between `min_responses` and `max_responses`. If `replication` is set, then the caller must ensure that `0 <= min_responses <= max_responses <= total_requests` and `1 <= total_requests <= N`, where `N` is the number of the nodes on the caller's subnet, otherwise the call will fail. The caller may use the `ic0.subnet_self_node_count` System API call to determine `N`. If `replication` is not provided, the defaults of `floor(2 / 3 * N) + 1`, `N` and `N` are used for `min_responses`, `max_responses` and `total_requests`. +- there is an additional optional argument `replication`. When set, the caller can specify how many nodes should issue an HTTP outcall (`total_requests`), the minimum number of HTTP responses from nodes in order for the outcall to succeed (`min_responses`), and the maximum number of HTTP responses the caller is willing to receive as the result of the outcall (`max_responses`). That is, a successful HTTP outcall is guaranteed to return between `min_responses` and `max_responses` responses. If `replication` is set, then the caller must ensure that `0 <= min_responses <= max_responses <= total_requests` and `1 <= total_requests <= N`, where `N` is the number of the nodes on the caller's subnet, otherwise the call will fail. The caller may use the `ic0.subnet_self_node_count` System API call to determine `N`. If `replication` is not provided, the defaults of `floor(2 / 3 * N) + 1`, `N` and `N` are used for `min_responses`, `max_responses` and `total_requests`. + + It is `min_responses` that determines when the outcall returns: the result is delivered as soon as `min_responses` responses are available, and further responses are included only if they have arrived by then, fit into the total result limit below, and are covered by the attached cycles. A successful outcall may therefore return as few as `min_responses` responses even when every node responded, so callers must handle any count in the permitted range. Setting `min_responses` and `max_responses` to `0` expresses a fire-and-forget outcall: the requests are issued, and the call replies with an empty vector as soon as the first node has reported back, regardless of the request's outcome. -- the deprecated `max_response_bytes` argument is not supported. +- the optional `max_response_bytes` argument bounds the size of the response, but it does not determine the cost of the call: flexible outcalls are always charged for the resources they actually consume. If provided, the value must not exceed `2MB` (`2,000,000B`), otherwise the call will fail. If not provided, the limit of `2MB` is used. Each node enforces the limit individually, both on the response received from the remote server and on the response produced by the `transform` function. The limit a node actually applies is the smaller of `max_response_bytes` and the response size its share of the attached cycles pays for, so a node may fail on a response that is within `max_response_bytes` if too few cycles were attached. -The other arguments, `url`, `method`, `headers`, `body`, and `transform` are the same as for `http_request`. The result is a vector of responses, with each individual response having the same structure as a `http_request` response, providing `status`, `headers`, and `body` fields. +The other arguments, `url`, `method`, `headers`, `body`, and `transform` are the same as for `http_request`. The result is a vector of responses, with each individual response having the same structure as a `http_request` response, providing `status`, `headers`, and `body` fields. Each response comes from a different node, but the responses do not identify the node that produced them, identical responses from different nodes are not merged, and the order of the responses in the vector is not specified. When fewer responses are returned than the nodes produced, which of them are returned is up to the system, so the returned responses must not be assumed to be a uniform sample. As for `http_request`, the endpoint specified by the provided `url` should be idempotent. The one exception is when `total_requests` is set to 1 in `replication`. The request restrictions are also the same as for the `http_request` method: - The total number of bytes in the request must not exceed `2MB` (`2,000,000`) bytes. -- Only the `GET`, `HEAD`, and `POST` methods are supported. +- The `GET`, `HEAD`, and `POST` methods are always supported. The `PUT`, `DELETE`, and `PATCH` methods are supported only when the replication counts are deterministic, i.e., when `min_responses`, `max_responses`, and `total_requests` are all equal; otherwise the call will fail. - The number of headers must not exceed `64`. @@ -731,29 +741,29 @@ As for `http_request`, the endpoint specified by the provided `url` should be id - The total number of bytes representing the header names and values must not exceed `48KiB`. -The response from the remote server must not exceed `2MB`. Moreover, the total size of the result, that is, the sum of the responses returned by the different replicas (possibly after the transform function), must also not exceed 2MB. +The response from the remote server must not exceed `max_response_bytes`, if provided, and `2MB` otherwise. Moreover, the responses returned by the different nodes (possibly after the transform function) are delivered together and must jointly fit into a total result limit of `2MiB` (`2,097,152B`), which applies to their encoded sizes plus a small per-response overhead. If they do not all fit, fewer responses are returned, down to `min_responses`; only when even the smallest `min_responses` responses exceed that limit does the call fail. Since up to `max_responses` responses are returned, choosing a `max_response_bytes` of at most `2MB / max_responses` keeps the result within the limit. -Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). The unused cycles are then refunded to the caller. +Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). As for `http_request` with pricing version `2`, a base fee is charged when the call is accepted and the remaining attached cycles bound what the nodes may spend on the outcall; the unused cycles are then refunded to the caller. -The method may return an error of the `flexible_http_request_err` type. The error includes a textual error message, an optional global error code, and a vector of resource reports from individual nodes. +The result of the call is a variant with an `ok` and an `err` arm, and both arms are delivered as a reply rather than as a reject: an outcall that cannot meet the requested replication requirements, including one that times out, replies with an `err` of the `flexible_http_request_err` type. That error includes a textual error message, an optional global error code, and a vector of per-node details. Failures detected before the requests are issued, such as invalid arguments, invalid `replication` counts, too few attached cycles, or the method not being available on the subnet, are delivered as a reject instead. The `global_error` field describes why the aggregate call failed to meet the requirements: - `timeout`, meaning that less than `min_responses` from the nodes have been collected before some system-defined timeout. -- `out_of_cycles` indicating that the attached cycles were not enough to cover the processing of at least `min_responses`. +- `out_of_cycles`, indicating that what the nodes left unspent of the attached cycles no longer covers delivering any result the call could still produce, including a `too_many_rejects` result. Since the cost of delivering a result depends on the sizes of the responses, this can be reported after the nodes have already completed their HTTP requests. -- `responses_too_large`: indicating that no combination of at least `min_responses` available responses could fit into the 2MB total limit. +- `responses_too_large`: indicating that no combination of at least `min_responses` available responses could fit into the total 2MiB result limit. -- `too_many_rejects`: indicating that more than `total_requests - min_responses` nodes returned reject responses, so at least `min_responses` successful responses can never be collected. +- `too_many_rejects`: indicating that more than `total_requests - min_responses` nodes returned reject responses, so at least `min_responses` successful responses can never be collected. A response, or a transform output, that exceeds the size limit a node enforces is rejected by that node, so exceeding that limit surfaces as `too_many_rejects` rather than as `responses_too_large`. -The `node_details` vector provides visibility into the execution on specific nodes. Each entry contains: +The `node_details` vector provides visibility into the execution on specific nodes; it may be empty, and it is not guaranteed to list every node the outcall was issued to. A `timeout` carries no entries; `too_many_rejects` lists rejecting nodes; `responses_too_large` and `out_of_cycles` list nodes whose responses the system has seen, whether those responses succeeded or were rejected. Each node appears at most once, and a successful outcall carries no per-node details at all. Each entry contains: - `node_id`. -- `report`: A detailed accounting of resources (bytes, instructions, time, and cycles) used by the node. Note: If a node fails due to a resource limit or running out of cycles, the corresponding field in this report will be set to `exceeded` rather than `used`. +- `report`: An accounting of resources (bytes, instructions, time, and cycles) used by the node. Every field is optional: a field is absent when the corresponding resource is not reported, `used` with the amount consumed, or `exceeded` if the node failed because that resource ran over its budget. An implementation may leave the whole report empty, so callers must not rely on it to diagnose a failure. -- `error`: An optional record containing a `code` and `message`. This is populated only when the node encounters a functional failure. +- `error`: An optional record containing a `code` and `message`. Its presence does not by itself indicate that the node failed: depending on the global error it is reported for every listed node, including nodes that responded successfully, in which case the `code` conveys the observed outcome and the `message` carries a size or a cycles figure. The `code` values are diagnostic strings and are not a fixed enumeration. ### IC method `node_metrics_history` {#ic-node_metrics_history} diff --git a/public/references/ic.did b/public/references/ic.did index f1289edf..d6371d3a 100644 --- a/public/references/ic.did +++ b/public/references/ic.did @@ -372,7 +372,7 @@ type deposit_cycles_args = record { type http_request_args = record { url : text; max_response_bytes : opt nat64; - method : variant { get; head; post; put; delete }; + method : variant { get; head; post; put; delete; patch }; headers : vec http_header; body : opt blob; transform : opt record { @@ -385,7 +385,8 @@ type http_request_args = record { type flexible_http_request_args = record { url : text; - method : variant { get; head; post }; + max_response_bytes : opt nat64; + method : variant { get; head; post; put; delete; patch }; headers : vec http_header; body : opt blob; transform : opt record {