diff --git a/docusaurus/package-lock.json b/docusaurus/package-lock.json index 001fe730..bd61ca59 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" } @@ -4447,9 +4429,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4459,7 +4441,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -5979,17 +5961,17 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } }, "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" @@ -6297,36 +6279,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6362,9 +6344,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -6521,12 +6503,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -9206,9 +9188,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -11094,11 +11079,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": { @@ -11349,9 +11334,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12494,11 +12482,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -13688,9 +13676,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13723,6 +13711,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -13835,14 +13831,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -15231,20 +15227,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 717fd680..60321cf5 100644 --- a/spec/_attachments/ic.did +++ b/spec/_attachments/ic.did @@ -76,6 +76,11 @@ type ecdsa_curve = variant { secp256k1; }; +type schnorr_algorithm = variant { + bip340secp256k1; + ed25519; +}; + type satoshi = nat64; type bitcoin_network = variant { @@ -85,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; @@ -107,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; }; @@ -140,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; @@ -303,6 +305,27 @@ type sign_with_ecdsa_result = record { signature : blob; }; +type schnorr_public_key_args = record { + canister_id : opt canister_id; + derivation_path : vec blob; + key_id : record { algorithm : schnorr_algorithm; name : text }; +}; + +type schnorr_public_key_result = record { + public_key : blob; + chain_code : blob; +}; + +type sign_with_schnorr_args = record { + message : blob; + derivation_path : vec blob; + key_id : record { algorithm : schnorr_algorithm; name : text }; +}; + +type sign_with_schnorr_result = record { + signature : blob; +}; + type node_metrics_history_args = record { subnet_id : principal; start_at_timestamp_nanos : nat64; @@ -335,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; }; @@ -377,13 +394,16 @@ service ic : { ecdsa_public_key : (ecdsa_public_key_args) -> (ecdsa_public_key_result); sign_with_ecdsa : (sign_with_ecdsa_args) -> (sign_with_ecdsa_result); + // Threshold Schnorr signature + schnorr_public_key : (schnorr_public_key_args) -> (schnorr_public_key_result); + sign_with_schnorr : (sign_with_schnorr_args) -> (sign_with_schnorr_result); + // 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 2cd3fb9a..aa6890b0 100644 --- a/spec/_attachments/interface-spec-changelog.md +++ b/spec/_attachments/interface-spec-changelog.md @@ -1,6 +1,11 @@ ## Changelog {#changelog} ### ∞ (unreleased) +* 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. ### 0.25.0 (2024-06-14) {#0_25_0} * Query call statistics. diff --git a/spec/index.md b/spec/index.md index ac7731da..a071707a 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 (asynchronous, potentially state-changing) calls. +- At `/api/v2/canister//call` the user can submit update calls that are asynchronous and might change the IC state. + +- At `/api/v3/canister//call` 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. @@ -1904,11 +1964,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} @@ -2083,12 +2143,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} @@ -2205,7 +2265,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. @@ -2273,11 +2333,13 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. -This method returns a [SEC1](https://www.secg.org/sec1-v2.pdf) encoded ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. Each byte string may be of arbitrary length, including empty. The total number of byte strings in the `derivation_path` must be at most 255. The `key_id` is a struct specifying both a curve and a name. The availability of a particular `key_id` depends on implementation. +This method returns a [SEC1](https://www.secg.org/sec1-v2.pdf) encoded ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. Each byte string may be of arbitrary length, including empty. The total number of byte strings in the `derivation_path` must be at most 255. The `key_id` is a struct specifying both a curve and a name. The availability of a particular `key_id` depends on the implementation. For curve `secp256k1`, the public key is derived using a generalization of BIP32 (see [ia.cr/2021/1330, Appendix D](https://ia.cr/2021/1330)). To derive (non-hardened) [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)-compatible public keys, each byte string (`blob`) in the `derivation_path` must be a 4-byte big-endian encoding of an unsigned integer less than 231. If the `derivation_path` contains a byte string that is not a 4-byte big-endian encoding of an unsigned integer less than 231, then a derived public key will be returned, but that key derivation process will not be compatible with the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) standard. -The return result is an extended public key consisting of an ECDSA `public_key`, encoded in [SEC1](https://www.secg.org/sec1-v2.pdf) compressed form, and a `chain_code`, which can be used to deterministically derive child keys of the `public_key`. +The return value is an extended public key consisting of an ECDSA `public_key`, encoded in [SEC1](https://www.secg.org/sec1-v2.pdf) compressed form, and a `chain_code`, which can be used to deterministically derive child keys of the `public_key`. + +This call requires that an ECDSA key with ID `key_id` was generated by the IC. Otherwise, the call is rejected. ### IC method `sign_with_ecdsa` {#ic-sign_with_ecdsa} @@ -2287,7 +2349,123 @@ This method returns a new [ECDSA](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FI The signatures are encoded as the concatenation of the [SEC1](https://www.secg.org/sec1-v2.pdf) encodings of the two values r and s. For curve `secp256k1`, this corresponds to 32-byte big-endian encoding. -This call requires that the ECDSA feature is enabled, the caller is a canister, and `message_hash` is 32 bytes long. Otherwise it will be rejected. +This call requires that an ECDSA key with ID `key_id` was generated by the IC, the signing functionality for that key was enabled, and `message_hash` is 32 bytes long. Otherwise, the call is is rejected. + +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). + +### IC method `schnorr_public_key` {#ic-schnorr_public_key} + +:::note + +Threshold Schnorr API is EXPERIMENTAL and there might be breaking changes of the behavior in the future. Use at your own risk! + +::: + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method returns a (derived) Schnorr public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. Each byte string may be of arbitrary length, including empty. The total number of byte strings in the `derivation_path` must be at most 255. The `key_id` is a struct specifying both an algorithm and a name. The availability of a particular `key_id` depends on the implementation. + +The return value is an extended Schnorr public key consisting of a Schnorr `public_key` and a `chain_code`. The chain code can be used to deterministically derive child keys of the `public_key`. Both the derivation and the encoding of the public key depends on the key ID's `algorithm`: + +- For algorithm `bip340secp256k1`, the public key is derived using the generalization of BIP32 defined in [ia.cr/2021/1330, Appendix D](https://ia.cr/2021/1330). To derive (non-hardened) [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)-compatible public keys, each byte string (`blob`) in the `derivation_path` must be a 4-byte big-endian encoding of an unsigned integer less than 231. If the `derivation_path` contains a byte string that is not a 4-byte big-endian encoding of an unsigned integer less than 231, then a derived public key will be returned, but that key derivation process will not be compatible with the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) standard. + + The public key is encoded in [SEC1](https://www.secg.org/sec1-v2.pdf) compressed form. To use BIP32 public keys to verify BIP340 Schnorr signatures, the first byte of the (33-byte) SEC1-encoded public key must be removed (see [BIP-340, Public Key Conversion](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#public-key-conversion)). + +- For algorithm `ed25519`, the public key is derived using the scheme specified in [Ed25519 hierarchical key derivation](#ed25519-key-derivation). + + The public key is encoded in standard 32-byte compressed form (see [RFC8032, 5.1.2 Encoding](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2)). + +This call requires that a Schnorr key with ID `key_id` was generated by the IC. Otherwise, the call is rejected. + +#### Ed25519 hierarchical key derivation {#ed25519-key-derivation} + +This section describes a child key derivation (CKD) function for computing child public keys from Ed25519 parent public keys. +The section is inspired by [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and uses similar wording and structure. + +##### Motivation + +To support the Ed25519 variant of threshold Schnorr signatures on the Internet Computer, a key derivation scheme compatible with Ed25519 signatures is required. +For a respective signing service on the Internet Computer to be efficient, the signing subnet maintains only a single master key pair and _derives_ signing child keys for each canister. +Although there exist various hierarchical key derivation schemes (e.g., [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), [SLIP10](https://github.com/satoshilabs/slips/blob/master/slip-0010.md), [BIP32-Ed25519](https://input-output-hk.github.io/adrestia/static/Ed25519_BIP.pdf), [Schnorrkel](https://github.com/w3f/schnorrkel)), all of the analyzed schemes are either incompatible in a threshold setting (e.g., use hardened key derivation only), comply with clamping which adds unnecessary complexity, or otherwise rely on non-standard primitives. +For these reasons, a new derivation scheme is specified here. +This scheme does not make use of _clamping_ (see [RFC8032, Section 5.1.5, Item 2](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5)), because it is unnecessary in the given setting, and satisfies the following requirements: + +- Off-chain availability: New public keys can be computed off-chain from a master public key without requiring interaction with the IC. +- Hierarchical derivation: Derived keys are organized in a tree such that from any public key it is possible to derive new child keys. The first level is used to derive unique canister-specific keys from the master key. +- Simplicity: The scheme is simple to implement using existing libraries. + +##### Conventions + +We will assume the elliptic curve (EC) operations using the field and curve parameters as defined by Ed25519 (see [RFC8032, Section 5.1](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1)). Variables below are either: + +- Integers modulo the order of the curve's prime order subgroup (referred to as L). +- Points on the curve. +- Byte sequences. + +Addition (+) of two points is defined as application of the EC group operation. +Concatenation (||) is the operation of appending one byte sequence onto another. + +We assume the following functions: + +- point(p): returns the point resulting from EC point multiplication (repeated application of the EC group operation) of the Ed25519 base point with the integer p. +- serP(P): serializes the point to a byte sequence using standard 32-byte compressed form (see [RFC8032, 5.1.2 Encoding](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2)). +- utf8(s): returns the UTF-8 encoding of string s. +- parse512(p): interprets a 64-byte sequence as a 512-bit number, most significant byte first. +- HKDF(salt,IKM,info,N) -> OKM: HMAC-based key derivation function (see [RFC5869](https://datatracker.ietf.org/doc/html/rfc5869)) using HMAC-SHA512 (see [RFC4231](https://datatracker.ietf.org/doc/html/rfc4231)) calculating N-bytes long output key material (OKM) from (byte sequences) salt, input key material (IKM), and application specific information *info*. + +##### Extended keys + +Public keys are extended with an extra 32 bytes of entropy, which extension is called chain code. +An extended public key is represented as (K, c), with K = point(k) and c being the chain code, for some private key k. +Each extended key can have an arbitrary number of child keys. +The scheme does not support hardened derivation of child keys. + +##### Child key derivation (CKD) function + +Given a parent extended public key and an index i, it is possible to compute the corresponding child extended public key. +The function CKDpub computes a child extended public key from a parent extended public key and an index i, where i is a byte sequence of arbitrary length (including empty). + +CKDpub((Kpar, cpar), i) → (Ki, ci): +- let IKM = serP(Kpar) || i. +- let OKM = HKDF(cpar, IKM, utf8("Ed25519"), 96). +- Split OKM into a 64-byte and a 32-byte sequence, tweak and ci. +- let Ki = Kpar + point(parse512(tweak) mod L). +- return (Ki, ci). + +##### Key tree + +A key tree can be built by repeatedly applying CKDpub, starting with one root, called the master extended public key M. +Computing CKDpub(M, i) for different values of i results in a number of level-0 derived keys. +As each of these is again an extended key, CKDpub can be applied to those as well. +The sequence of indices used when repeatedly applying CKDpub is called the _derivation path_. + +The function KTpub computes a child extended public key from a parent extended public key and a derivation path d. + +KTpub((Kpar, cpar), d) → (Kd, cd): +- let (Kd, cd) = (Kpar, cpar) +- for all indices i in d: + (Kd, cd) = CKDpub((Kd, cd), i) +- return (Kd, cd). + +### IC method `sign_with_schnorr` {#ic-sign_with_schnorr} + +:::note + +Threshold Schnorr API is EXPERIMENTAL and there might be breaking changes of the behavior in the future. Use at your own risk! + +::: + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method returns a Schnorr signature of the given `message` that can be verified against a (derived) public key obtained by calling `schnorr_public_key` using the caller's `canister_id` and the given `derivation_path` and `key_id`. + +The encoding of the signature depends on the key ID's `algorithm`: + +- For algorithm `bip340secp256k1`, the signature is encoded in 64 bytes according to [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). + +- For algorithm `ed25519`, the signature is encoded in 64 bytes according to [RFC8032, 5.1.6 Sign](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.6). + +This call requires that a Schnorr key with ID `key_id` was generated by the IC and the signing functionality for that key was enabled. Otherwise, the call is is rejected. 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). @@ -2390,29 +2568,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} @@ -2490,35 +2674,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} @@ -2657,9 +2859,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; } @@ -2864,6 +3066,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; @@ -3029,9 +3232,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 @@ -3222,13 +3425,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. @@ -3305,7 +3533,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. @@ -3349,7 +3577,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. @@ -3395,25 +3623,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;} ) @@ -3532,19 +3749,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: @@ -3580,19 +3785,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) ``` @@ -3632,19 +3825,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) ``` @@ -3685,19 +3866,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) ``` @@ -3757,6 +3926,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; @@ -3822,7 +3992,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] @@ -3833,7 +4004,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 @@ -4078,18 +4250,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 @@ -4114,7 +4275,7 @@ State after ```html -S with +S' = S with canisters[Canister_id] = EmptyCanister time[Canister_id] = CurrentTime global_timer[Canister_id] = 0 @@ -4209,19 +4370,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; @@ -4248,7 +4397,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 @@ -4322,7 +4471,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, ); @@ -4418,7 +4568,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 @@ -4511,6 +4661,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; @@ -4523,38 +4674,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] @@ -4581,7 +4709,7 @@ State after ```html -S with +S' = S with canisters[A.canister_id] = { wasm_state = New_state; module = Mod; @@ -4642,6 +4770,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]; @@ -4649,10 +4778,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]; } ( @@ -4703,38 +4834,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] @@ -4760,7 +4868,7 @@ State after ```html -S with +S' = S with canisters[A.canister_id] = { wasm_state = New_state; module = Mod; @@ -5155,6 +5263,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 @@ -5320,18 +5429,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 @@ -5356,7 +5454,7 @@ State after ```html -S with +S' = S with canisters[Canister_id] = EmptyCanister time[Canister_id] = CurrentTime global_timer[Canister_id] = 0 @@ -5828,6 +5926,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]; @@ -5836,6 +5935,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; @@ -5849,19 +5949,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) @@ -6050,7 +6138,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): ``` @@ -6168,6 +6256,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 = { @@ -6731,20 +6835,7 @@ I ∈ {i32, i64} ic0.cycles_burn128(amount_high : i64, amount_low : i64, dst : I) = 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()) @@ -6841,17 +6932,7 @@ ic0.call_data_append (src : I, size : I) = 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 @@ -6860,17 +6941,7 @@ 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;} + 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 @@ -6883,17 +6954,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