diff --git a/docusaurus/package-lock.json b/docusaurus/package-lock.json index 001fe730..9aaae4ab 100644 --- a/docusaurus/package-lock.json +++ b/docusaurus/package-lock.json @@ -3569,24 +3569,6 @@ "@types/ms": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", - "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -4058,10 +4040,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "peerDependencies": { "acorn": "^8" } @@ -5987,9 +5969,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -11094,11 +11076,11 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -15231,20 +15213,19 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/spec/_attachments/ic.did b/spec/_attachments/ic.did index 702b7ef4..60321cf5 100644 --- a/spec/_attachments/ic.did +++ b/spec/_attachments/ic.did @@ -90,7 +90,13 @@ type bitcoin_network = variant { type bitcoin_address = text; -type block_hash = blob; +type bitcoin_block_hash = blob; + +type bitcoin_block_header = blob; + +type millisatoshi_per_byte = nat64; + +type bitcoin_block_height = nat32; type outpoint = record { txid : blob; @@ -112,30 +118,10 @@ type bitcoin_get_utxos_args = record { }; }; -type bitcoin_get_utxos_query_args = record { - address : bitcoin_address; - network : bitcoin_network; - filter : opt variant { - min_confirmations : nat32; - page : blob; - }; -}; - -type bitcoin_get_current_fee_percentiles_args = record { - network : bitcoin_network; -}; - type bitcoin_get_utxos_result = record { utxos : vec utxo; - tip_block_hash : block_hash; - tip_height : nat32; - next_page : opt blob; -}; - -type bitcoin_get_utxos_query_result = record { - utxos : vec utxo; - tip_block_hash : block_hash; - tip_height : nat32; + tip_block_hash : bitcoin_block_hash; + tip_height : bitcoin_block_height; next_page : opt blob; }; @@ -145,18 +131,29 @@ type bitcoin_get_balance_args = record { min_confirmations : opt nat32; }; -type bitcoin_get_balance_query_args = record { - address : bitcoin_address; +type bitcoin_get_balance_result = satoshi; + +type bitcoin_get_current_fee_percentiles_args = record { network : bitcoin_network; - min_confirmations : opt nat32; }; +type bitcoin_get_current_fee_percentiles_result = vec millisatoshi_per_byte; + type bitcoin_send_transaction_args = record { transaction : blob; network : bitcoin_network; }; -type millisatoshi_per_byte = nat64; +type bitcoin_get_block_headers_args = record { + start_height : bitcoin_block_height; + end_height : opt bitcoin_block_height; + network: bitcoin_network; +}; + +type bitcoin_get_block_headers_result = record { + tip_height : bitcoin_block_height; + block_headers : vec bitcoin_block_header; +}; type node_metrics = record { node_id : principal; @@ -361,12 +358,6 @@ type stored_chunks_result = vec chunk_hash; type upload_chunk_result = chunk_hash; -type bitcoin_get_balance_result = satoshi; - -type bitcoin_get_balance_query_result = satoshi; - -type bitcoin_get_current_fee_percentiles_result = vec millisatoshi_per_byte; - type fetch_canister_logs_args = record { canister_id : canister_id; }; @@ -409,11 +400,10 @@ service ic : { // bitcoin interface bitcoin_get_balance : (bitcoin_get_balance_args) -> (bitcoin_get_balance_result); - bitcoin_get_balance_query : (bitcoin_get_balance_query_args) -> (bitcoin_get_balance_query_result) query; bitcoin_get_utxos : (bitcoin_get_utxos_args) -> (bitcoin_get_utxos_result); - bitcoin_get_utxos_query : (bitcoin_get_utxos_query_args) -> (bitcoin_get_utxos_query_result) query; bitcoin_send_transaction : (bitcoin_send_transaction_args) -> (); bitcoin_get_current_fee_percentiles : (bitcoin_get_current_fee_percentiles_args) -> (bitcoin_get_current_fee_percentiles_result); + bitcoin_get_block_headers : (bitcoin_get_block_headers_args) -> (bitcoin_get_block_headers_result); // metrics interface node_metrics_history : (node_metrics_history_args) -> (node_metrics_history_result); diff --git a/spec/_attachments/interface-spec-changelog.md b/spec/_attachments/interface-spec-changelog.md index 4ac935f2..25494821 100644 --- a/spec/_attachments/interface-spec-changelog.md +++ b/spec/_attachments/interface-spec-changelog.md @@ -1,5 +1,9 @@ ## Changelog {#changelog} +### 0.27.0 (2024-09-20) {#0_27_0} +* EXPERIMENTAL: Management canister API to fetch Bitcoin block headers. +* Synchronous update call API at `/api/v3/canister/.../call`. + ### 0.26.0 (2024-07-23) {#0_26_0} * EXPERIMENTAL: Management canister API for threshold Schnorr signatures. diff --git a/spec/index.md b/spec/index.md index 21655d68..6c9f8c44 100644 --- a/spec/index.md +++ b/spec/index.md @@ -76,7 +76,7 @@ The public entry points of canisters are called *methods*. Methods can be declar Methods can be *called*, from *caller* to *callee*, and will eventually incur a *response* which is either a *reply* or a *reject*. A method may have *parameters*, which are provided with concrete *arguments* in a method call. -External calls can be update calls, which can *only* call update and query methods, and query calls, which can *only* call query and composite query methods. Inter-canister calls issued while evaluating an update call can call update and query methods (just like update calls). Inter-canister calls issued while evaluating a query call (to a composite query method) can call query and composite query methods (just like query calls). Note that calls from a canister to itself also count as "inter-canister". Update and query call offer a security/efficiency trade-off. +External calls can be update calls, which can *only* call update and query methods, and query calls, which can *only* call query and composite query methods. Inter-canister calls issued while evaluating an update call can call update and query methods (just like update calls). Inter-canister calls issued while evaluating a query call (to a composite query method) can call query and composite query methods (just like query calls). Note that calls from a canister to itself also count as "inter-canister". Update and query call offer a security/efficiency trade-off. Update calls are executed in *replicated* mode, i.e. execution takes place in parallel on multiple replicas who need to arrive at a consensus on what the result of the call is. Query calls are fast but offer less guarantees since they are executed in *non-replicated* mode, by a single replica. Internally, a call or a response is transmitted as a *message* from a *sender* to a *receiver*. Messages do not have a response. @@ -109,11 +109,11 @@ This specification may refer to certain constants and limits without specifying - `CHUNK_STORE_SIZE` - Maximum number of chunks that can be stored within the chunk store of a canister. + Maximum number of chunks that can be stored within the chunk store of a canister. - `MAX_CHUNKS_IN_LARGE_WASM` - Maximum number of chunks that can comprise a large Wasm module. + Maximum number of chunks that can comprise a large Wasm module. - `DEFAULT_PROVISIONAL_CYCLES_BALANCE` @@ -448,7 +448,7 @@ The state tree contains information about all API boundary nodes (the source of Example: `api-bn1.example.com`. - `/api_boundary_nodes//ipv4_address` (text) - + Public IPv4 address of a node in the dotted-decimal notation. If no `ipv4_address` is available for the corresponding node, then this path does not exist. Example: `192.168.10.150`. @@ -479,12 +479,12 @@ The state tree contains information about the topology of the Internet Computer. - `/subnet//metrics` (blob) A collection of subnet-wide metrics related to this subnet's current resource usage and/or performance. The metrics are a CBOR map with the following fields: - + - `num_canisters` (`nat`): The number of canisters on this subnet. - `canister_state_bytes` (`nat`): The total size of the state in bytes taken by canisters on this subnet since this subnet was created. - `consumed_cycles_total` (`map`): The total number of cycles consumed by all current and deleted canisters on this subnet. It's a map of two values, a low part of type `nat` and a high part of type `opt nat`. - `update_transactions_total` (`nat`): The total number of transactions processed on this subnet since this subnet was created. - + :::note @@ -498,7 +498,7 @@ Because this uses the lexicographic ordering of princpials, and the byte disting ### Request status {#state-tree-request-status} -For each asynchronous request known to the Internet Computer, its status is in a subtree at `/request_status/`. Please see [Overview of canister calling](#http-call-overview) for more details on how asynchronous requests work. +For each update call request known to the Internet Computer, its status is in a subtree at `/request_status/`. Please see [Overview of canister calling](#http-call-overview) for more details on how update call requests work. - `/request_status//status` (text) @@ -558,9 +558,11 @@ Users have the ability to learn about the hash of the canister's module, its cur ## HTTPS Interface {#http-interface} -The concrete mechanism that users use to send requests to the Internet Computer is via an HTTPS API, which exposes three endpoints to handle interactions, plus one for diagnostics: +The concrete mechanism that users use to send requests to the Internet Computer is via an HTTPS API, which exposes four endpoints to handle interactions, plus one for diagnostics: + +- At `/api/v2/canister//call` the user can submit update calls that are asynchronous and might change the IC state. -- At `/api/v2/canister//call` the user can submit (asynchronous, potentially state-changing) calls. +- At `/api/v3/canister//call` the user can submit update calls and get a synchronous HTTPS response with a certificate for the call status. - At `/api/v2/canister//read_state` or `/api/v2/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. @@ -570,7 +572,7 @@ The concrete mechanism that users use to send requests to the Internet Computer In these paths, the `` is the [textual representation](#textual-ids) of the [*effective* canister id](#http-effective-canister-id). -Requests to `/api/v2/canister//call`, `/api/v2/canister//read_state`, `/api/v2/subnet//read_state`, and `/api/v2/canister//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. +Requests to `/api/v2/canister//call`, `/api/v3/canister//call`, `/api/v2/canister//read_state`, `/api/v2/subnet//read_state`, and `/api/v2/canister//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. :::note @@ -580,7 +582,13 @@ This document does not yet explain how to find the location and port of the Inte ### Overview of canister calling {#http-call-overview} -Users interact with the Internet Computer by calling canisters. By the very nature of a blockchain protocol, they cannot be acted upon immediately, but only with a delay. Moreover, the actual node that the user talks to may not be honest or, for other reasons, may fail to get the request on the way. This implies the following high-level workflow: +Users interact with the Internet Computer by calling canisters. By the very nature of a blockchain protocol, they cannot be acted upon immediately, but only with a delay. Moreover, the actual node that the user talks to may not be honest or, for other reasons, may fail to get the request on the way. + +The Internet Computer has two HTTPS APIs for canister calling: +- [*Asynchronous*](#http-async-call-overview) canister calling, where the user must poll the Internet Computer for the status of the canister call by _separate_ HTTPS requests. +- [*Synchronous*](#http-sync-call-overview) canister calling, where the status of the canister call is in the response of the original HTTPS request. + +#### Asynchronous canister calling {#http-async-call-overview} 1. A user submits a call via the [HTTPS Interface](#http-interface). No useful information is returned in the immediate response (as such information cannot be trustworthy anyways). @@ -639,8 +647,60 @@ Calls must stay in `replied` or `rejected` long enough for polling users to catc When asking the IC about the state or call of a request, the user uses the request id (see [Request ids](#request-id)) to read the request status (see [Request status](#state-tree-request-status)) from the state tree (see [Request: Read state](#http-read-state)). +#### Synchronous canister calling {#http-sync-call-overview} + +A synchronous update call, also known as a "call and await", is a type of update call where the replica will attempt to respond to the HTTPS request with a certificate of the call status. If the returned certificate indicates that the update call is in a terminal state (`replied`, `rejected`, or `done`), then the user __does not need to poll__ (using [`read_state`](#http-read-state) requests) to determine the result of the call. A terminal state means the call has completed its execution. + +The synchronous call endpoint is useful for users as it removes the networking overhead of polling the IC to determine the status of their call. + +The replica will maintain the HTTPS connection for the request and will respond once the call status transitions to a terminal state. + +If an implementation specific timeout for the request is reached while the replica waits for the terminal state, then the replica will reply with an empty body and a 202 HTTP status code. In such cases, the user should use [`read_state`](#http-read-state) to determine the status of the call. + ### Request: Call {#http-call} +In order to call a canister, the user makes a POST request to `/api/v3/canister//call`. The request body consists of an authentication envelope with a `content` map with the following fields: + +- `request_type` (`text`): Always `call` + +- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication) + +- `canister_id` (`blob`): The principal of the canister to call. + +- `method_name` (`text`): Name of the canister method to call. + +- `arg` (`blob`): Argument to pass to the canister method. + +The HTTP response to this request can have the following forms: + +- 200 HTTP status with a non-empty body. This status is returned if the canister call completed within an implementation-specific timeout or was rejected within an implementation-specific timeout. + + - If the update call completed, a certificate for the state of the update call is produced, and returned in a CBOR (see [CBOR](#cbor)) map with the fields specified below: + + - `status` (`text`): `"certified_state"` + + - `reply` (`blob`): A certificate (see [Certification](#certification)) with subtrees at `/request_status/` and `/time`, where `` is the [request ID](#request-id) of the update call. See [Request status](#state-tree-request-status) for more details on the request status. + + - If a non-replicated pre-processing error occurred (e.g., due to the [canister inspect message](#system-api-inspect-message)), then a body with information about the IC specific error encountered is returned. The body is a CBOR map with the following fields: + + - `status` (`text`): `"non_replicated_rejection"` + + - `reject_code` (`nat`): The reject code (see [Reject codes](#reject-codes)). + + - `reject_message` (`text`): a textual diagnostic message. + + - `error_code` (`text`): an optional implementation-specific textual error code (see [Error codes](#error-codes)). + +- 202 HTTP status with an empty body. This status is returned if an implementation-specific timeout is reached before the canister call completes. Users should use [`read_state`](#http-read-state) to determine the status of the call. + +- 4xx HTTP status for client errors (e.g. malformed request). Except for 429 HTTP status, retrying the request will likely have the same outcome. + +- 5xx HTTP status when the server has encountered an error or is otherwise incapable of performing the request. The request might succeed if retried at a later time. + +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 cycle balance change due to message execution). + +### Request: Asynchronous Call {#http-async-call} + In order to call a canister, the user makes a POST request to `/api/v2/canister//call`. The request body consists of an authentication envelope with a `content` map with the following fields: - `request_type` (`text`): Always `call` @@ -899,7 +959,7 @@ All requests coming in via the HTTPS interface need to be either *anonymous* or - `nonce` (`blob`, optional): Arbitrary user-provided data of length at most 32 bytes, typically randomly generated. This can be used to create distinct requests with otherwise identical fields. -- `ingress_expiry` (`nat`, required): An upper limit on the validity of the request, expressed in nanoseconds since 1970-01-01 (like [ic0.time()](#system-api-time)). This avoids replay attacks: The IC will not accept requests, or transition requests from status `received` to status `processing`, if their expiry date is in the past. The IC may refuse to accept requests with an ingress expiry date too far in the future. This applies to synchronous and asynchronous requests alike (and could have been called `request_expiry`). +- `ingress_expiry` (`nat`, required): An upper limit on the validity of the request, expressed in nanoseconds since 1970-01-01 (like [ic0.time()](#system-api-time)). This avoids replay attacks: The IC will not accept requests, or transition requests from status `received` to status `processing`, if their expiry date is in the past. The IC may refuse to accept requests with an ingress expiry date too far in the future. This applies not only to update calls, but all requests alike (and could have been called `request_expiry`). - `sender` (`Principal`, required): The user who issued the request. @@ -1884,11 +1944,11 @@ In the future, the IC might expose more performance counters. ### Replicated execution check {#system-api-replicated-execution-check} -The canister can check whether it is currently running in replicated or non replicated execution. +The canister can check whether it is currently running in replicated or non replicated execution. `ic0.in_replicated_execution : () -> (result: i32)` -Returns 1 if the canister is being run in replicated mode and 0 otherwise. +Returns 1 if the canister is being run in replicated mode and 0 otherwise. ### Controller check {#system-api-controller-check} @@ -2063,12 +2123,12 @@ The optional `sender_canister_version` parameter can contain the caller's canist This method can be called by canisters as well as by external users via ingress messages. Canisters have associated some storage space (hence forth chunk storage) where they can hold chunks of Wasm modules that are too lage to fit in a single message. This method allows the controllers of a canister (and the canister itself) to upload such chunks. The method returns the hash of the chunk that was stored. The size of each chunk must be at most 1MiB. The maximum number of chunks in the chunk store is `CHUNK_STORE_SIZE` chunks. The storage cost of each chunk is fixed and corresponds to storing 1MiB of data. - + ### IC method `clear_chunk_store` {#ic-clear_chunk_store} This method can be called by canisters as well as by external users via ingress messages. -Canister controllers (and the canister itself) can clear the entire chunk storage of a canister. +Canister controllers (and the canister itself) can clear the entire chunk storage of a canister. ### IC method `stored_chunks` {#ic-stored_chunks} @@ -2185,7 +2245,7 @@ Indicates various information about the canister. It contains: Only the controllers of the canister or the canister itself can request its status. -### IC method `canister_info` {#ic-canister-info} +### IC method `canister_info` {#ic-canister_info} This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. @@ -2488,29 +2548,35 @@ A single metric entry is a record with the following fields: - `num_block_failures_total` (`nat64`): the number of failed block proposals by this node. -### IC method `provisional_create_canister_with_cycles` {#ic-provisional_create_canister_with_cycles} - -This method can be called by canisters as well as by external users via ingress messages. +### IC method `fetch_canister_logs` {#ic-fetch_canister_logs} -As a provisional method on development instances, the `provisional_create_canister_with_cycles` method is provided. It behaves as `create_canister`, but initializes the canister's balance with `amount` fresh cycles (using `DEFAULT_PROVISIONAL_CYCLES_BALANCE` if `amount = null`). If `specified_id` is provided, the canister is created under this id. Note that canister creation using `create_canister` or `provisional_create_canister_with_cycles` with `specified_id = null` can fail after calling `provisional_create_canister_with_cycles` with provided `specified_id`. In that case, canister creation should be retried. +This method can only be called by external users via non-replicated calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls. -The optional `sender_canister_version` parameter can contain the caller's canister version. If provided, its value must be equal to `ic0.canister_version`. +:::note -Cycles added to this call via `ic0.call_cycles_add` and `ic0.call_cycles_add128` are returned to the caller. +The canister logs management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. -This method is only available in local development instances. +::: -### IC method `provisional_top_up_canister` {#ic-provisional_top_up_canister} +Given a canister ID as input, this method returns a vector of logs of that canister including its trap messages. +The canister logs are *not* collected in canister methods running in non-replicated mode (NRQ, CQ, CRy, CRt, CC, and F modes, as defined in [Overview of imports](#system-api-imports)) and the canister logs are *purged* when the canister is reinstalled or uninstalled. +The total size of all returned logs does not exceed 4KiB. +If new logs are added resulting in exceeding the maximum total log size of 4KiB, the oldest logs will be removed. +Logs persist across canister upgrades and they are deleted if the canister is reinstalled or uninstalled. +The log visibility is defined in the `log_visibility` field of `canister_settings`: logs can be either public (visible to everyone) or only visible to the canister's controllers (by default). -This method can be called by canisters as well as by external users via ingress messages. +A single log is a record with the following fields: -As a provisional method on development instances, the `provisional_top_up_canister` method is provided. It adds `amount` cycles to the balance of canister identified by `amount`. +- `idx` (`nat64`): the unique sequence number of the log for this particular canister; +- `timestamp_nanos` (`nat64`): the timestamp as nanoseconds since 1970-01-01 at which the log was recorded; +- `content` (`blob`): the actual content of the log; -Cycles added to this call via `ic0.call_cycles_add` and `ic0.call_cycles_add128` are returned to the caller. +:::warning -Any user can top-up any canister this way. +The response of a query comes from a single replica, and is therefore not appropriate for security-sensitive applications. +Replica-signed queries may improve security because the recipient can verify the response comes from the correct subnet. -This method is only available in local development instances. +::: ## The IC Bitcoin API {#ic-bitcoin-api} @@ -2588,35 +2654,53 @@ This function returns fee percentiles, measured in millisatoshi/vbyte (1000 mill The [standard nearest-rank estimation method](https://en.wikipedia.org/wiki/Percentile#The_nearest-rank_method), inclusive, with the addition of a 0th percentile is used. Concretely, for any i from 1 to 100, the ith percentile is the fee with rank `⌈i * 100⌉`. The 0th percentile is defined as the smallest fee (excluding coinbase transactions). -### IC method `fetch_canister_logs` {#ic-fetch_canister_logs} - -This method can only be called by external users via non-replicated calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls. +### IC method `bitcoin_get_block_headers` {#ic-bitcoin_get_block_headers} :::note -The canister logs management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. +The `bitcoin_get_block_headers` endpoint is considered EXPERIMENTAL. Canister developers must be aware that this endpoint may evolve in a non-backward-compatible way. ::: -Given a canister ID as input, this method returns a vector of logs of that canister including its trap messages. -The canister logs are *not* collected in canister methods running in non-replicated mode (NRQ, CQ, CRy, CRt, CC, and F modes, as defined in [Overview of imports](#system-api-imports)) and the canister logs are *purged* when the canister is reinstalled or uninstalled. -The total size of all returned logs does not exceed 4KiB. -If new logs are added resulting in exceeding the maximum total log size of 4KiB, the oldest logs will be removed. -Logs persist across canister upgrades and they are deleted if the canister is reinstalled or uninstalled. -The log visibility is defined in the `log_visibility` field of `canister_settings`: logs can be either public (visible to everyone) or only visible to the canister's controllers (by default). +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. -A single log is a record with the following fields: +Given a start height, an optional end height, and a Bitcoin network (`mainnet` or `testnet`), the function returns the block headers in the provided range. The range is inclusive, i.e., the block headers at the start and end heights are returned as well. +An error is returned when an end height is specified that is greater than the tip height. -- `idx` (`nat64`): the unique sequence number of the log for this particular canister; -- `timestamp_nanos` (`nat64`): the timestamp as nanoseconds since 1970-01-01 at which the log was recorded; -- `content` (`blob`): the actual content of the log; +If no end height is specified, all blocks until the tip height, i.e., the largest available height, are returned. However, if the range from the start height to the end height or the tip height is large, only a prefix of the requested block headers may be returned in order to bound the size of the response. -:::warning +The response is guaranteed to contain the block headers in order: if it contains any block headers, the first block header occurs at the start height, the second block header occurs at the start height plus one and so forth. -The response of a query comes from a single replica, and is therefore not appropriate for security-sensitive applications. -Replica-signed queries may improve security because the recipient can verify the response comes from the correct subnet. +The response is a record consisting of the tip height and the vector of block headers. +The block headers are 80-byte blobs in the [standard Bitcoin format](https://developer.bitcoin.org/reference/block_chain.html#block-headers). -::: +## The IC Provisional API {#ic-provisional-api} + +The IC Provisional API for creating canisters and topping up canisters out of thin air is only available in local development instances. + +### IC method `provisional_create_canister_with_cycles` {#ic-provisional_create_canister_with_cycles} + +This method can be called by canisters as well as by external users via ingress messages. + +As a provisional method on development instances, the `provisional_create_canister_with_cycles` method is provided. It behaves as `create_canister`, but initializes the canister's balance with `amount` fresh cycles (using `DEFAULT_PROVISIONAL_CYCLES_BALANCE` if `amount = null`). If `specified_id` is provided, the canister is created under this id. Note that canister creation using `create_canister` or `provisional_create_canister_with_cycles` with `specified_id = null` can fail after calling `provisional_create_canister_with_cycles` with provided `specified_id`. In that case, canister creation should be retried. + +The optional `sender_canister_version` parameter can contain the caller's canister version. If provided, its value must be equal to `ic0.canister_version`. + +Cycles added to this call via `ic0.call_cycles_add` and `ic0.call_cycles_add128` are returned to the caller. + +This method is only available in local development instances. + +### IC method `provisional_top_up_canister` {#ic-provisional_top_up_canister} + +This method can be called by canisters as well as by external users via ingress messages. + +As a provisional method on development instances, the `provisional_top_up_canister` method is provided. It adds `amount` cycles to the balance of canister identified by `amount`. + +Cycles added to this call via `ic0.call_cycles_add` and `ic0.call_cycles_add128` are returned to the caller. + +Any user can top-up any canister this way. + +This method is only available in local development instances. ## Certification {#certification} @@ -2755,9 +2839,9 @@ A certificate by the root subnet does not have a delegation field. A certificate The certificate included in the delegation (if present) must not itself again contain a delegation. ::: + ``` -Delegation = - Delegation { +Delegation = { subnet_id : Principal; certificate : Certificate; } @@ -2962,6 +3046,7 @@ The [WebAssembly System API](#system-api) is relatively low-level, and some of i memory_allocation : Nat; memory_usage_raw_module : Nat; memory_usage_canister_history : Nat; + memory_usage_chunk_store : Nat; freezing_threshold : Nat; subnet_size : Nat; certificate : NoCertificate | Blob; @@ -3127,9 +3212,9 @@ A reference implementation would likely maintain a separate list of `messages` f #### API requests -We distinguish between the *asynchronous* API requests (type `Request`) passed to `/api/v2/…/call`, which may be present in the IC state, and the *synchronous* API requests passed to `/api/v2/…/read_state` and `/api/v2/…/query`, which are only ephemeral. +We distinguish between API requests (type `Request`) passed to `/api/v2/…/call` and `/api/v3/…/call`, which may be present in the IC state, and the *read-only* API requests passed to `/api/v2/…/read_state` and `/api/v2/…/query`, which are only ephemeral. -These are the synchronous read messages: +These are the read-only messages: ``` Path = List(Blob) APIReadRequest @@ -3320,13 +3405,38 @@ The (unspecified) function `idle_cycles_burned_rate(compute_allocation, memory_a freezing_limit(compute_allocation, memory_allocation, freezing_threshold, memory_usage, subnet_size) = idle_cycles_burned_rate(compute_allocation, memory_allocation, memory_usage, subnet_size) * freezing_threshold / (24 * 60 * 60) ``` -The (unspecified) functions `memory_usage_wasm_state(wasm_state)`, `memory_usage_raw_module(raw_module)`, and `memory_usage_canister_history(canister_history)` determine the canister's memory usage in bytes consumed by its Wasm state, raw Wasm binary, and canister history, respectively. +The (unspecified) functions `memory_usage_wasm_state(wasm_state)`, `memory_usage_raw_module(raw_module)`, `memory_usage_canister_history(canister_history)`, and `memory_usage_chunk_store(chunk_store)` determine the canister's memory usage in bytes consumed by its Wasm state, raw Wasm binary, canister history, and chunk store, respectively. + +The freezing limit of canister `A` in state `S` can be obtained as follows: +``` +freezing_limit(S, A) = + freezing_limit( + S.compute_allocation[A], + S.memory_allocation[A], + S.freezing_threshold[A], + memory_usage_wasm_state(S.canisters[A].wasm_state) + + memory_usage_raw_module(S.canisters[A].raw_module) + + memory_usage_canister_history(S.canister_history[A]) + + memory_usage_chunk_store(S.chunk_store[A]), + S.canister_subnet[A].subnet_size, + ) +``` The amount of cycles that is available for spending in calls and execution is computed by the function `liquid_balance(balance, reserved_balance, freezing_limit)`: ``` liquid_balance(balance, reserved_balance, freezing_limit) = balance - max(freezing_limit - reserved_balance, 0) ``` +The "liquid" balance of canister `A` in state `S` can be obtained as follows: +``` +liquid_balance(S, A) = + liquid_balance( + S.balances[A], + S.reserved_balances[A], + freezing_limit(S, A), + ) +``` + The reasoning behind this is that resource payments first drain the reserved balance and only when the reserved balance gets to zero, they start draining the main balance. The amount of cycles that need to be reserved after operations that allocate resources is modeled with an unspecified function `cycles_to_reserve(S, CanisterId, compute_allocation, memory_allocation, CanState)` that depends on the old IC state, the id of the canister, the new allocations of the canister, and the new state of the canister. @@ -3403,7 +3513,7 @@ The following is an incomplete list of invariants that should hold for the abstr Based on this abstract notion of the state, we can describe the behavior of the IC. There are three classes of behaviors: -- Asynchronous API requests that are submitted via `/api/v2/…/call`. These transitions describe checks that the request must pass to be considered received. +- Potentially state changing API requests that are submitted via `/api/v2/…/call` and `/api/v3/…/call`. These transitions describe checks that the request must pass to be considered received. - Spontaneous transitions that model the internal behavior of the IC, by describing conditions on the state that allow the transition to happen, and the state after. @@ -3447,7 +3557,7 @@ is_effective_canister_id(Request {canister_id = p, …}, p), if p ≠ ic_princip ``` #### API Request submission -After a node accepts a request via `/api/v2/canister//call`, the request gets added to the IC state as `Received`. +After a node accepts a request via `/api/v2/canister//call` or `/api/v3/canister//call`, the request gets added to the IC state as `Received`. This may only happen if the signature is valid and is created with a correct key. Due to this check, the envelope is discarded after this point. @@ -3493,25 +3603,14 @@ is_effective_canister_id(E.content, ECID) memory_allocation = S.memory_allocation[E.content.canister_id]; memory_usage_raw_module = memory_usage_raw_module(S.canisters[E.content.canister_id].raw_module); memory_usage_canister_history = memory_usage_canister_history(S.canister_history[E.content.canister_id]); + memory_usage_chunk_store = memory_usage_chunk_store(S.chunk_store[E.content.canister_id]); freezing_threshold = S.freezing_threshold[E.content.canister_id]; subnet_size = S.canister_subnet[E.content.canister_id].subnet_size; certificate = NoCertificate; status = simple_status(S.canister_status[E.content.canister_id]); canister_version = S.canister_version[E.content.canister_id]; } - liquid_balance( - S.balances[E.content.canister_id], - S.reserved_balances[E.content.canister_id], - freezing_limit( - S.compute_allocation[E.content.canister_id], - S.memory_allocation[E.content.canister_id], - S.freezing_threshold[E.content.canister_id], - memory_usage_wasm_state(S.canisters[E.content.canister_id].wasm_state) + - memory_usage_raw_module(S.canisters[E.content.canister_id].raw_module) + - memory_usage_canister_history(S.canister_history[E.content.canister_id]), - S.canister_subnet[E.content.canister_id].subnet_size, - ) - ) ≥ 0 + liquid_balance(S, E.content.canister_id) ≥ 0 S.canisters[E.content.canister_id].module.inspect_message (E.content.method_name, S.canisters[E.content.canister_id].wasm_state, E.content.arg, E.content.sender, Env) = Return {status = Accept;} ) @@ -3630,19 +3729,7 @@ Conditions S.messages = Older_messages · CallMessage CM · Younger_messages (CM.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ CM.queue) S.canisters[CM.callee] ≠ EmptyCanister -S.canister_status[CM.callee] = liquid_balance( - S.balances[CM.callee], - S.reserved_balances[CM.callee], - freezing_limit( - S.compute_allocation[CM.callee], - S.memory_allocation[CM.callee], - S.freezing_threshold[CM.callee], - memory_usage_wasm_state(S.canisters[CM.callee].wasm_state) + - memory_usage_raw_module(S.canisters[CM.callee].raw_module) + - memory_usage_canister_history(S.canister_history[CM.callee]), - S.canister_subnet[CM.callee].subnet_size, - ) -) < 0 +liquid_balance(S, CM.callee) < 0 ``` State after: @@ -3678,19 +3765,7 @@ S.messages = Older_messages · CallMessage CM · Younger_messages (CM.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ CM.queue) S.canisters[CM.callee] ≠ EmptyCanister S.canister_status[CM.callee] = Running -liquid_balance( - S.balances[CM.callee], - S.reserved_balances[CM.callee], - freezing_limit( - S.compute_allocation[CM.callee], - S.memory_allocation[CM.callee], - S.freezing_threshold[CM.callee], - memory_usage_wasm_state(S.canisters[CM.callee].wasm_state) + - memory_usage_raw_module(S.canisters[CM.callee].raw_module) + - memory_usage_canister_history(S.canister_history[CM.callee]), - S.canister_subnet[CM.callee].subnet_size, - ) -) ≥ MAX_CYCLES_PER_MESSAGE +liquid_balance(S, CM.callee) ≥ MAX_CYCLES_PER_MESSAGE Ctxt_id ∉ dom(S.call_contexts) ``` @@ -3730,19 +3805,7 @@ Conditions S.canisters[C] ≠ EmptyCanister S.canister_status[C] = Running -liquid_balance( - S.balances[C], - S.reserved_balance[C], - freezing_limit( - S.compute_allocation[C], - S.memory_allocation[C], - S.freezing_threshold[C], - memory_usage_wasm_state(S.canisters[C].wasm_state) + - memory_usage_raw_module(S.canisters[C].raw_module) + - memory_usage_canister_history(S.canister_history[C]), - S.canister_subnet[C].subnet_size, - ) -) ≥ MAX_CYCLES_PER_MESSAGE +liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE Ctxt_id ∉ dom(S.call_contexts) ``` @@ -3783,19 +3846,7 @@ S.canisters[C] ≠ EmptyCanister S.canister_status[C] = Running S.global_timer[C] ≠ 0 S.time[C] ≥ S.global_timer[C] -liquid_balance( - S.balances[C], - S.reserved_balances[C], - freezing_limit( - S.compute_allocation[C], - S.memory_allocation[C], - S.freezing_threshold[C], - memory_usage_wasm_state(S.canisters[C].wasm_state) + - memory_usage_raw_module(S.canisters[C].raw_module) + - memory_usage_canister_history(S.canister_history[C]), - S.canister_subnet[C].subnet_size, - ) -) ≥ MAX_CYCLES_PER_MESSAGE +liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE Ctxt_id ∉ dom(S.call_contexts) ``` @@ -3855,6 +3906,7 @@ Env = { memory_allocation = S.memory_allocation[M.receiver]; memory_usage_raw_module = memory_usage_raw_module(S.canisters[M.receiver].raw_module); memory_usage_canister_history = memory_usage_canister_history(S.canister_history[M.receiver]); + memory_usage_chunk_store = memory_usage_chunk_store(S.chunk_store[M.receiver]); freezing_threshold = S.freezing_threshold[M.receiver]; subnet_size = S.canister_subnet[M.receiver].subnet_size; certificate = NoCertificate; @@ -3920,7 +3972,8 @@ if S.freezing_threshold[M.receiver], memory_usage_wasm_state(res.new_state) + memory_usage_raw_module(S.canisters[M.receiver].raw_module) + - memory_usage_canister_history(S.canister_history[M.receiver]), + memory_usage_canister_history(S.canister_history[M.receiver]) + + memory_usage_chunk_store(S.chunk_store[M.receiver]), S.canister_subnet[M.receiver].subnet_size, ) New_reserved_balance ≤ S.reserved_balance_limits[M.receiver] @@ -3931,7 +3984,8 @@ if ) ≥ 0 (S.memory_allocation[M.receiver] = 0) or (memory_usage_wasm_state(res.new_state) + memory_usage_raw_module(S.canisters[M.receiver].raw_module) + - memory_usage_canister_history(S.canister_history[M.receiver]) ≤ S.memory_allocation[M.receiver]) + memory_usage_canister_history(S.canister_history[M.receiver]) + + memory_usage_chunk_store(S.chunk_store[M.receiver]) ≤ S.memory_allocation[M.receiver]) (Wasm_memory_limit = 0) or |res.new_state.store.mem| <= Wasm_memory_limit (res.response = NoResponse) or S.call_contexts[M.call_context].needs_to_respond then @@ -4176,18 +4230,7 @@ New_balance = M.transferred_cycles - Cycles_reserved New_reserved_balance = Cycles_reserved New_reserved_balance <= New_reserved_balance_limit if New_compute_allocation > 0 or New_memory_allocation > 0 or Cycles_reserved > 0: - liquid_balance( - New_balance, - New_reserved_balance, - freezing_limit( - New_compute_allocation, - New_memory_allocation, - New_freezing_threshold, - memory_usage_canister_history(New_canister_history), - SubnetSize, - ) - ) ≥ 0 - + liquid_balance(S', Canister_id) ≥ 0 New_canister_history = { total_num_changes = 1 @@ -4212,7 +4255,7 @@ State after ```html -S with +S' = S with canisters[Canister_id] = EmptyCanister time[Canister_id] = CurrentTime global_timer[Canister_id] = 0 @@ -4307,19 +4350,7 @@ New_balance = S.balances[A.canister_id] - Cycles_reserved New_reserved_balance = S.reserved_balances[A.canister_id] + Cycles_reserved New_reserved_balance ≤ New_reserved_balance_limit if New_compute_allocation > S.compute_allocation[A.canister_id] or New_memory_allocation > S.memory_allocation[A.canister_id] or Cycles_reserved > 0: - liquid_balance( - New_balance, - New_reserved_balance, - freezing_limit( - New_compute_allocation, - New_memory_allocation, - New_freezing_threshold, - memory_usage_wasm_state(S.canisters[A.canister_id].wasm_state) + - memory_usage_raw_module(S.canisters[A.canister_id].raw_module) + - memory_usage_canister_history(New_canister_history), - S.canister_subnet[A.canister_id].subnet_size, - ) - ) ≥ 0 + liquid_balance(S', A.canister_id) ≥ 0 S.canister_history[A.canister_id] = { total_num_changes = N; @@ -4346,7 +4377,7 @@ State after ```html -S with +S' = S with if A.settings.controllers is not null: controllers[A.canister_id] = A.settings.controllers canister_history[A.canister_id] = New_canister_history @@ -4420,7 +4451,8 @@ S with S.memory_allocation[A.canister_id], memory_usage_wasm_state(S.canisters[A.canister_id].wasm_state) + memory_usage_raw_module(S.canisters[A.canister_id].raw_module) + - memory_usage_canister_history(S.canister_history[A.canister_id]), + memory_usage_canister_history(S.canister_history[A.canister_id]) + + memory_usage_chunk_store(S.chunk_store[A.canister_id]), S.freezing_threshold[A.canister_id], S.canister_subnet[A.canister_id].subnet_size, ); @@ -4516,7 +4548,7 @@ S with #### IC Management Canister: Clear chunk store -The controller of a canister, or the canister itself can clear the chunk store of that canister. +The controller of a canister, or the canister itself can clear the chunk store of that canister. ```html @@ -4609,6 +4641,7 @@ Env = { memory_allocation = S.memory_allocation[A.canister_id]; memory_usage_raw_module = memory_usage_raw_module(A.wasm_module); memory_usage_canister_history = memory_usage_canister_history(New_canister_history); + memory_usage_chunk_store = memory_usage_chunk_store(New_chunk_store); freezing_threshold = S.freezing_threshold[A.canister_id]; subnet_size = S.canister_subnet[A.canister_id].subnet_size; certificate = NoCertificate; @@ -4621,38 +4654,15 @@ New_balance = S.balances[A.canister_id] - Cycles_used - Cycles_reserved New_reserved_balance = S.reserved_balances[A.canister_id] + Cycles_reserved New_reserved_balance ≤ S.reserved_balance_limits[A.canister_id] -liquid_balance( - S.balances[A.canister_id], - S.reserved_balances[A.canister_id], - freezing_limit( - S.compute_allocation[A.canister_id], - S.memory_allocation[A.canister_id], - S.freezing_threshold[A.canister_id], - memory_usage_wasm_state(S.canisters[A.canister_id].wasm_state) + - memory_usage_raw_module(S.canisters[A.canister_id].raw_module) + - memory_usage_canister_history(S.canister_history[A.canister_id]), - S.canister_subnet[A.canister_id].subnet_size, - ) -) ≥ MAX_CYCLES_PER_MESSAGE +liquid_balance(S, A.canister_id) ≥ MAX_CYCLES_PER_MESSAGE -liquid_balance( - New_balance, - New_reserved_balance, - freezing_limit( - S.compute_allocation[A.canister_id], - S.memory_allocation[A.canister_id], - S.freezing_threshold[A.canister_id], - memory_usage_wasm_state(New_state) + - memory_usage_raw_module(A.wasm_module) + - memory_usage_canister_history(New_canister_history), - S.canister_subnet[A.canister_id].subnet_size, - ) -) ≥ 0 +liquid_balance(S', A.canister_id) ≥ 0 if S.memory_allocation[A.canister_id] > 0: memory_usage_wasm_state(New_state) + memory_usage_raw_module(A.wasm_module) + - memory_usage_canister_history(New_canister_history) ≤ S.memory_allocation[A.canister_id] + memory_usage_canister_history(New_canister_history) + + memory_usage_chunk_store(New_chunk_store) ≤ S.memory_allocation[A.canister_id] (S.wasm_memory_limit[A.canister_id] = 0) or |New_state.store.mem| <= S.wasm_memory_limit[A.canister_id] @@ -4679,7 +4689,7 @@ State after ```html -S with +S' = S with canisters[A.canister_id] = { wasm_state = New_state; module = Mod; @@ -4740,6 +4750,7 @@ dom(Mod.query_methods) ∩ dom(Mod.composite_query_methods) = ∅ Env = { time = S.time[A.canister_id]; controllers = S.controllers[A.canister_id]; + global_timer = S.global_timer[A.canister_id]; balance = S.balances[A.canister_id]; reserved_balance = S.reserved_balances[A.canister_id]; reserved_balance_limit = S.reserved_balance_limits[A.canister_id]; @@ -4747,10 +4758,12 @@ Env = { memory_allocation = S.memory_allocation[A.canister_id]; memory_usage_raw_module = memory_usage_raw_module(S.canisters[A.canister_id].raw_module); memory_usage_canister_history = memory_usage_canister_history(S.canister_history[A.canister_id]); + memory_usage_chunk_store = memory_usage_chunk_store(S.chunk_store[A.canister_id]); freezing_threshold = S.freezing_threshold[A.canister_id]; subnet_size = S.canister_subnet[A.canister_id].subnet_size; certificate = NoCertificate; status = simple_status(S.canister_status[A.canister_id]); + canister_version = S.canister_version[A.canister_id]; } ( @@ -4801,38 +4814,15 @@ New_balance = S.balances[A.canister_id] - Cycles_used - Cycles_used' - Cycles_re New_reserved_balance = S.reserved_balances[A.canister_id] + Cycles_reserved New_reserved_balance ≤ S.reserved_balance_limits[A.canister_id] -liquid_balance( - S.balances[A.canister_id], - S.reserved_balances[A.canister_id], - freezing_limit( - S.compute_allocation[A.canister_id], - S.memory_allocation[A.canister_id], - S.freezing_threshold[A.canister_id], - memory_usage_wasm_state(S.canisters[A.canister_id].wasm_state) + - memory_usage_raw_module(S.canisters[A.canister_id].raw_module) + - memory_usage_canister_history(S.canister_history[A.canister_id]), - S.canister_subnet[A.canister_id].subnet_size, - ) -) ≥ MAX_CYCLES_PER_MESSAGE +liquid_balance(S, A.canister_id) ≥ MAX_CYCLES_PER_MESSAGE -liquid_balance( - New_balance, - New_reserved_balance, - freezing_limit( - S.compute_allocation[A.canister_id], - S.memory_allocation[A.canister_id], - S.freezing_threshold[A.canister_id], - memory_usage_wasm_state(New_state) + - memory_usage_raw_module(A.wasm_module) + - memory_usage_canister_history(New_canister_history), - S.canister_subnet[A.canister_id].subnet_size, - ) -) ≥ 0 +liquid_balance(S', A.canister_id) ≥ 0 if S.memory_allocation[A.canister_id] > 0: memory_usage_wasm_state(New_state) + memory_usage_raw_module(A.wasm_module) + - memory_usage_canister_history(New_canister_history) ≤ S.memory_allocation[A.canister_id] + memory_usage_canister_history(New_canister_history) + + memory_usage_chunk_store(S[A.canister_id].chunk_store) ≤ S.memory_allocation[A.canister_id] (S.wasm_memory_limit[A.canister_id] = 0) or |New_state.store.mem| <= S.wasm_memory_limit[A.canister_id] @@ -4858,7 +4848,7 @@ State after ```html -S with +S' = S with canisters[A.canister_id] = { wasm_state = New_state; module = Mod; @@ -5253,6 +5243,7 @@ S with canister_log_visibility[A.canister_id] = (deleted) canister_logs[A.canister_id] = (deleted) query_stats[A.canister_id] = (deleted) + chunk_store[A.canister_id] = (deleted) messages = Older_messages · Younger_messages · ResponseMessage { origin = M.origin @@ -5418,18 +5409,7 @@ else: New_reserved_balance = Cycles_reserved New_reserved_balance ≤ New_reserved_balance_limit if New_compute_allocation > 0 or New_memory_allocation > 0 or Cycles_reserved > 0: - liquid_balance( - New_balance, - New_reserved_balance, - freezing_limit( - New_compute_allocation, - New_memory_allocation, - New_freezing_threshold, - memory_usage_canister_history(New_canister_history), - SubnetSize, - ) - ) ≥ 0 - + liquid_balance(S', Canister_id) ≥ 0 New_canister_history { total_num_changes = 1 @@ -5454,7 +5434,7 @@ State after ```html -S with +S' = S with canisters[Canister_id] = EmptyCanister time[Canister_id] = CurrentTime global_timer[Canister_id] = 0 @@ -5926,6 +5906,7 @@ composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Canister_id, then Cert := NoCertificate // no certificate available in query and composite query methods evaluated on canisters other than the target canister of the query call let Env = { time = S.time[Canister_id]; + controllers = S.controllers[Canister_id]; global_timer = S.global_timer[Canister_id]; balance = S.balances[Canister_id]; reserved_balance = S.reserved_balances[Canister_id]; @@ -5934,6 +5915,7 @@ composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Canister_id, memory_allocation = S.memory_allocation[Canister_id]; memory_usage_raw_module = memory_usage_raw_module(S.canisters[Canister_id].raw_module); memory_usage_canister_history = memory_usage_canister_history(S.canister_history[Canister_id]); + memory_usage_chunk_store = memory_usage_chunk_store(S.chunk_store[Canister_id]); freezing_threshold = S.freezing_threshold[Canister_id]; subnet_size = S.canister_subnet[Canister_id].subnet_size; certificate = Cert; @@ -5947,19 +5929,7 @@ composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Canister_id, then let W = S.canisters[Canister_id].wasm_state let F = if Method_name ∈ dom(Mod.query_methods) then Mod.query_methods[Method_name] else Mod.composite_query_methods[Method_name] - if liquid_balance( - S.balances[Canister_id], - S.reserved_balances[Canister_id], - freezing_limit( - S.compute_allocation[Canister_id], - S.memory_allocation[Canister_id], - S.freezing_threshold[Canister_id], - memory_usage_wasm_state(S.canisters[Canister_id].wasm_state) + - memory_usage_raw_module(S.canisters[Canister_id].raw_module) + - memory_usage_canister_history(S.canister_history[Canister_id]), - S.canister_subnet[Canister_id].subnet_size, - ) - ) < 0 + if liquid_balance(S, Canister_id) < 0 then Return (Reject (SYS_TRANSIENT, ), Cycles, S) let R = F(Arg, Caller, Env)(W) @@ -6148,7 +6118,7 @@ Read response A record with - `{certificate: C}` - + The predicate `may_read_path_for_subnet` is defined as follows, implementing the access control outlined in [Request: Read state](#http-read-state): ``` @@ -6265,6 +6235,22 @@ It is nonsensical to pass to an execution function a WebAssembly store `S` that ::: +The "liquid" balance of a canister with a given `ExecutionState` can be obtained as follows: +``` +liquid_balance(es) = + liquid_balance( + es.balance, + es.params.sysenv.reserved_balance, + freezing_limit( + es.params.sysenv.compute_allocation, + es.params.sysenv.memory_allocation, + es.params.sysenv.freezing_threshold, + memory_usage_wasm_state(es.wasm_state) + es.params.sysenv.memory_usage_raw_module + es.params.sysenv.memory_usage_canister_history + es.params.sysenv.memory_usage_chunk_store, + es.params.sysenv.subnet_size, + ) + ) +``` + - For more convenience when creating a new `ExecutionState`, we define the following partial records: ``` empty_params = { @@ -6813,20 +6799,7 @@ ic0.msg_cycles_accept128(max_amount_high : i64, max_amount_low : i64, dst : ic0.cycles_burn128(amount_high : i64, amount_low : i64, dst : i32) = if es.context ∉ {I, G, U, Ry, Rt, C, T} then Trap {cycles_used = es.cycles_used;} let amount = amount_high * 2^64 + amount_low - let burned_amount = min( - amount, - liquid_balance( - es.balance, - es.params.sysenv.reserved_balance, - freezing_limit( - es.params.sysenv.compute_allocation, - es.params.sysenv.memory_allocation, - es.params.sysenv.freezing_threshold, - memory_usage_wasm_state(es.wasm_state) + es.params.sysenv.memory_usage_raw_module + es.params.sysenv.memory_usage_canister_history, - es.params.sysenv.subnet_size, - ) - ) - ) + let burned_amount = min(amount, liquid_balance(es)) es.balance := es.balance - burned_amount copy_cycles_to_canister(dst, burned_amount.to_little_endian_bytes()) @@ -6915,36 +6888,16 @@ ic0.call_data_append (src : i32, size : i32) = ic0.call_cycles_add(amount : i64) = if es.context ∉ {U, Ry, Rt, T} then Trap {cycles_used = es.cycles_used;} if es.pending_call = NoPendingCall then Trap {cycles_used = es.cycles_used;} - if liquid_balance( - es.balance, - es.params.sysenv.reserved_balance, - freezing_limit( - es.params.sysenv.compute_allocation, - es.params.sysenv.memory_allocation, - es.params.sysenv.freezing_threshold, - memory_usage_wasm_state(es.wasm_state) + es.params.sysenv.memory_usage_raw_module + es.params.sysenv.memory_usage_canister_history, - es.params.sysenv.subnet_size, - ) - ) < amount then Trap {cycles_used = es.cycles_used;} + if liquid_balance(es) < amount then Trap {cycles_used = es.cycles_used;} es.balance := es.balance - amount es.pending_call.transferred_cycles := es.pending_call.transferred_cycles + amount - ic0.call_cycles_add128(amount_high : i64, amount_low : i64) = - if es.context ∉ {U, Ry, Rt, T} then Trap {cycles_used = es.cycles_used;} - if es.pending_call = NoPendingCall then Trap {cycles_used = es.cycles_used;} - let amount = amount_high * 2^64 + amount_low - if liquid_balance( - es.balance, - es.params.sysenv.reserved_balance, - freezing_limit( - es.params.sysenv.compute_allocation, - es.params.sysenv.memory_allocation, - es.params.sysenv.freezing_threshold, - memory_usage_wasm_state(es.wasm_state) + es.params.sysenv.memory_usage_raw_module + es.params.sysenv.memory_usage_canister_history, - es.params.sysenv.subnet_size, - ) - ) < amount then Trap {cycles_used = es.cycles_used;} +ic0.call_cycles_add128(amount_high : i64, amount_low : i64) = + if es.context ∉ {U, Ry, Rt, T} then Trap {cycles_used = es.cycles_used;} + if es.pending_call = NoPendingCall then Trap {cycles_used = es.cycles_used;} + let amount = amount_high * 2^64 + amount_low + if liquid_balance(es) < amount then Trap {cycles_used = es.cycles_used;} es.balance := es.balance - amount es.pending_call.transferred_cycles := es.pending_call.transferred_cycles + amount @@ -6957,17 +6910,7 @@ ic0.call_peform() : ( err_code : i32 ) = // are we below the freezing threshold? // Or maybe the system has other reasons to not perform this - if liquid_balance( - es.balance, - es.params.sysenv.reserved_balance, - freezing_limit( - es.params.sysenv.compute_allocation, - es.params.sysenv.memory_allocation, - es.params.sysenv.freezing_threshold, - memory_usage_wasm_state(es.wasm_state) + es.params.sysenv.memory_usage_raw_module + es.params.sysenv.memory_usage_canister_history, - es.params.sysenv.subnet_size, - ) - ) < 0 or system_cannot_do_this_call_now() + if liquid_balance(es) < 0 or system_cannot_do_this_call_now() then discard_pending_call() return