diff --git a/docusaurus/package-lock.json b/docusaurus/package-lock.json index 001fe730..f48909f1 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" @@ -5130,9 +5112,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -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.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "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": "0.7.1", "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", @@ -7685,9 +7667,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -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 f0dd16dc..8c43e699 100644 --- a/spec/_attachments/ic.did +++ b/spec/_attachments/ic.did @@ -1,5 +1,6 @@ type canister_id = principal; type wasm_module = blob; +type snapshot_id = blob; type log_visibility = variant { controllers; @@ -46,6 +47,11 @@ type change_details = variant { mode : variant { install; reinstall; upgrade }; module_hash : blob; }; + load_snapshot : record { + canister_version : nat64; + snapshot_id : snapshot_id; + taken_at_timestamp : nat64; + }; controllers_change : record { controllers : vec principal; }; @@ -126,22 +132,6 @@ type bitcoin_get_utxos_result = record { next_page : opt blob; }; -type bitcoin_get_utxos_query_args = record { - address : bitcoin_address; - network : bitcoin_network; - filter : opt variant { - min_confirmations : nat32; - page : blob; - }; -}; - -type bitcoin_get_utxos_query_result = record { - utxos : vec utxo; - tip_block_hash : bitcoin_block_hash; - tip_height : bitcoin_block_height; - next_page : opt blob; -}; - type bitcoin_get_balance_args = record { address : bitcoin_address; network : bitcoin_network; @@ -150,14 +140,6 @@ type bitcoin_get_balance_args = record { type bitcoin_get_balance_result = satoshi; -type bitcoin_get_balance_query_args = record { - address : bitcoin_address; - network : bitcoin_network; - min_confirmations : opt nat32; -}; - -type bitcoin_get_balance_query_result = satoshi; - type bitcoin_get_current_fee_percentiles_args = record { network : bitcoin_network; }; @@ -383,6 +365,36 @@ type stored_chunks_result = vec chunk_hash; type upload_chunk_result = chunk_hash; +type snapshot = record { + id : snapshot_id; + taken_at_timestamp : nat64; + total_size : nat64; +}; + +type take_canister_snapshot_args = record { + canister_id : canister_id; + replace_snapshot : opt snapshot_id; +}; + +type take_canister_snapshot_result = snapshot; + +type load_canister_snapshot_args = record { + canister_id : canister_id; + snapshot_id : snapshot_id; + sender_canister_version : opt nat64; +}; + +type list_canister_snapshots_args = record { + canister_id : canister_id; +}; + +type list_canister_snapshots_result = vec snapshot; + +type delete_canister_snapshot_args = record { + canister_id : canister_id; + snapshot_id : snapshot_id; +}; + type fetch_canister_logs_args = record { canister_id : canister_id; }; @@ -425,9 +437,7 @@ 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); @@ -439,6 +449,12 @@ service ic : { provisional_create_canister_with_cycles : (provisional_create_canister_with_cycles_args) -> (provisional_create_canister_with_cycles_result); provisional_top_up_canister : (provisional_top_up_canister_args) -> (); + // Canister snapshots + take_canister_snapshot : (take_canister_snapshot_args) -> (take_canister_snapshot_result); + load_canister_snapshot : (load_canister_snapshot_args) -> (); + list_canister_snapshots : (list_canister_snapshots_args) -> (list_canister_snapshots_result); + delete_canister_snapshot : (delete_canister_snapshot_args) -> (); + // canister logging fetch_canister_logs : (fetch_canister_logs_args) -> (fetch_canister_logs_result) query; }; diff --git a/spec/_attachments/interface-spec-changelog.md b/spec/_attachments/interface-spec-changelog.md index 94e5d694..c89cb5a0 100644 --- a/spec/_attachments/interface-spec-changelog.md +++ b/spec/_attachments/interface-spec-changelog.md @@ -1,7 +1,14 @@ ## Changelog {#changelog} ### ∞ (unreleased) +* Allow anonymous query and read state requests with invalid `ingress_expiry`. + +### 0.28.0 (2024-10-11) {#0_28_0} +* Add new management canister methods for canister snapshot support. + +### 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 cd11ca5a..3c840ddc 100644 --- a/spec/index.md +++ b/spec/index.md @@ -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` @@ -871,6 +931,8 @@ It must be contained in the canister ranges of a subnet, otherwise the correspon - If the call is to the `provisional_create_canister_with_cycles` method, then any principal can be used as the effective canister id for this call. + - If the call is to the `install_chunked_code` method and the `arg` is a Candid-encoded record with a `target_canister` field of type `principal`, then the effective canister id must be that principal. + - Otherwise, if the `arg` is a Candid-encoded record with a `canister_id` field of type `principal`, then the effective canister id must be that principal. - Otherwise, the call is rejected by the system independently of the effective canister id. @@ -899,7 +961,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. These rules for ingress expiry apply not only to update calls but all requests alike (and could have been called `request_expiry`), except for anonymous `query` and anonymous `read_state` requests for which the IC may accept any provided expiry timestamp. - `sender` (`Principal`, required): The user who issued the request. @@ -1573,7 +1635,7 @@ This function allows a canister to find out if it is running, stopping or stoppe ### Canister version {#system-api-canister-version} -For each canister, the system maintains a *canister version*. Upon canister creation, it is set to 0, and it is **guaranteed** to be incremented upon every change of the canister's code, settings, running status (Running, Stopping, Stopped), and memory (WASM and stable memory), i.e., upon every successful management canister call of methods `update_settings`, `install_code`, `install_chunked_code`, `uninstall_code`, `start_canister`, and `stop_canister` on that canister, code uninstallation due to that canister running out of cycles, canister's running status transitioning from Stopping to Stopped, and successful execution of update methods, response callbacks, heartbeats, and global timers. The system can arbitrarily increment the canister version also if the canister's code, settings, running status, and memory do not change. +For each canister, the system maintains a *canister version*. Upon canister creation, it is set to 0, and it is **guaranteed** to be incremented upon every change of the canister's code, settings, running status (Running, Stopping, Stopped), and memory (WASM and stable memory), i.e., upon every successful management canister call of methods `update_settings`, `load_canister_snapshot`, `install_code`, `install_chunked_code`, `uninstall_code`, `start_canister`, and `stop_canister` on that canister, code uninstallation due to that canister running out of cycles, canister's running status transitioning from Stopping to Stopped, and successful execution of update methods, response callbacks, heartbeats, and global timers. The system can arbitrarily increment the canister version also if the canister's code, settings, running status, and memory do not change. - `ic0.canister_version : () → i64` @@ -1658,7 +1720,7 @@ There must be at most one call to `ic0.call_on_cleanup` between `ic0.call_new` a This deducts `MAX_CYCLES_PER_RESPONSE` cycles from the canister balance and sets them aside for response processing. - If the function returns `0` as the `err_code`, the IC was able to enqueue the call. In this case, the call will either be delivered, returned because the destination canister does not exist or returned because of an out of cycles condition. This also means that exactly one of the reply or reject callbacks will be executed. + If the function returns `0` as the `err_code`, the IC was able to enqueue the call. In this case, the call will either be delivered, returned because the destination canister does not exist, returned due to a lack of resources within the IC, or returned because of an out of cycles condition. This also means that exactly one of the reply or reject callbacks will be executed. If the function returns a non-zero value, the call cannot (and will not be) performed. This can happen due to a lack of resources within the IC, but also if it would reduce the current cycle balance to a level below where the canister would be frozen. @@ -2191,7 +2253,7 @@ This method can only be called by canisters, i.e., it cannot be called by extern Provides the history of the canister, its current module SHA-256 hash, and its current controllers. Every canister can call this method on every other canister (including itself). Users cannot call this method. -The canister history consists of a list of canister changes (canister creation, code uninstallation, code deployment, or controllers change). Every canister change consists of the system timestamp at which the change was performed, the canister version after performing the change, the change's origin (a user or a canister), and its details. The change origin includes the principal (called *originator* in the following) that initiated the change and, if the originator is a canister, the originator's canister version when the originator initiated the change (if available). Code deployments are described by their mode (code install, code reinstall, code upgrade) and the SHA-256 hash of the newly deployed canister module. Canister creations and controllers changes are described by the full new set of the canister controllers after the change. The order of controllers stored in the canister history may vary depending on the implementation. +The canister history consists of a list of canister changes (canister creation, code uninstallation, code deployment, snapshot restoration, or controllers change). Every canister change consists of the system timestamp at which the change was performed, the canister version after performing the change, the change's origin (a user or a canister), and its details. The change origin includes the principal (called *originator* in the following) that initiated the change and, if the originator is a canister, the originator's canister version when the originator initiated the change (if available). Code deployments are described by their mode (code install, code reinstall, code upgrade) and the SHA-256 hash of the newly deployed canister module. Loading a snapshot is described by the canister version, snapshot ID and timestamp at which the snapshot was taken. Canister creations and controllers changes are described by the full new set of the canister controllers after the change. The order of controllers stored in the canister history may vary depending on the implementation. The system can drop the oldest canister changes from the list to keep its length bounded (at least `20` changes are guaranteed to remain in the list). The system also drops all canister changes if the canister runs out of cycles. @@ -2488,6 +2550,58 @@ 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 `take_canister_snapshot` {#ic-take_canister_snapshot} + +This method can be called by canisters as well as by external users via ingress messages. + +This method takes a snapshot of the specified canister. A snapshot consists of the wasm memory, stable memory, certified variables, wasm chunk store and wasm binary. + +Subsequent `take_canister_snapshot` calls will create a new snapshot. However, a `take_canister_snapshot` call might fail if the maximum number of snapshots per canister is reached. This error can be avoided by providing a snapshot ID via the optional `replace_snapshot` parameter. The snapshot identified by the specified ID will be deleted once a new snapshot has been successfully created. Currently, only one snapshot per canister is allowed. + +It's important to note that a snapshot will increase the memory footprint of the canister. Thus, the canister's balance must have a sufficient amount of cycles to not become frozen. + +Only controllers can take a snapshot of a canister and load it back to the canister. + +:::note + +It's important to stop a canister before taking a snapshot to ensure that all outstanding callbacks are completed. Failing to do so may cause the canister to not make sense of the callbacks if its state is restored using the snapshot. +It is expected that the canister controllers (or their tooling) do this separately. + +::: + +### IC method `load_canister_snapshot` {#ic-load_canister_snapshot} + +This method can be called by canisters as well as by external users via ingress messages. + +This method loads a snapshot identified by `snapshot_id` onto the canister. It fails if no snapshot with the specified `snapshot_id` can be found. + +Only controllers can take a snapshot of a canister and load it back to the canister. + +:::note + +It's important to stop a canister before loading a snapshot to ensure that all outstanding callbacks are completed. Failing to do so may cause the canister to not make sense of the callbacks if its state is restored. +It is expected that the canister controllers (or their tooling) do this separately. + +::: + +The optional `sender_canister_version` parameter can contain the caller's canister version. If provided, its value must be equal to `ic0.canister_version`. + +### IC method `list_canister_snapshots` {#ic-list_canister_snapshots} + +This method can be called by canisters as well as by external users via ingress messages. + +This method lists the snapshots of the canister identified by `canister_id`. Only controllers of the canister can list its snapshots. Currently, at most one snapshot per canister will be stored. + +### IC method `delete_canister_snapshot` {#ic-delete_canister_snapshot} + +This method can be called by canisters as well as by external users via ingress messages. + +This method deletes a specified snapshot that belongs to an existing canister. An error will be returned if the snapshot is not found. + +A snapshot cannot be found if it was never created, it was previously deleted, replaced by a new snapshot through a `take_canister_snapshot` request, or if the canister itself has been deleted or run out of cycles. + +A snapshot may be deleted only by the controllers of the canister for which the snapshot was taken. + ### 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. @@ -2991,6 +3105,8 @@ 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; + memory_usage_snapshot : Nat; freezing_threshold : Nat; subnet_size : Nat; certificate : NoCertificate | Blob; @@ -3156,9 +3272,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 @@ -3247,6 +3363,7 @@ CodeDeploymentMode = Install | Reinstall | Upgrade +SnapshotId = (abstract) ChangeDetails = Creation { controllers : [PrincipalId]; @@ -3256,6 +3373,11 @@ ChangeDetails mode : CodeDeploymentMode; module_hash : Blob; } + | LoadSnapshot { + canister_version : CanisterVersion; + snapshot_id : SnapshotId; + taken_at_timestamp : Timestamp; + } | ControllersChange { controllers : [PrincipalId]; } @@ -3288,9 +3410,19 @@ Subnet = { subnet_id : Principal; subnet_size : Nat; } +Snapshot = { + snapshot_id : SnapshotId; + wasm_state : WasmState; + raw_module : Blob; + chunk_store : ChunkStore; + certified_data : Blob; + canister_version : CanisterVersion; + taken_at_timestamp : Timestamp; +} S = { requests : Request ↦ (RequestStatus, Principal); canisters : CanisterId ↦ CanState; + snapshots: CanisterId ↦ Snapshot; controllers : CanisterId ↦ Set Principal; compute_allocation : CanisterId ↦ Nat; memory_allocation : CanisterId ↦ Nat; @@ -3350,24 +3482,52 @@ 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)`, `memory_usage_chunk_store(chunk_store)`, and `memory_usage_snapshot(snapshot)` determine the canister's memory usage in bytes consumed by its Wasm state, raw Wasm binary, canister history, chunk store, and snapshot, 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]) + + memory_usage_snapshot(S.snapshots[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. +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, snapshots, CanState)` that depends on the old IC state, the id of the canister, the new allocations of the canister, the snapshots of the canister, and the new state of the canister. #### Initial state The initial state of the IC is + ``` { requests = (); canisters = (); + snapshots = (); controllers = (); compute_allocation = (); memory_allocation = (); @@ -3433,7 +3593,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. @@ -3472,12 +3632,13 @@ delegation_targets(D) A `Request` has an effective canister id according to the rules in [Effective canister id](#http-effective-canister-id): ``` is_effective_canister_id(Request {canister_id = ic_principal, method = provisional_create_canister_with_cycles, …}, p) +is_effective_canister_id(Request {canister_id = ic_principal, method = install_chunked_code, arg = candid({target_canister = p, …}), …}, p) is_effective_canister_id(Request {canister_id = ic_principal, arg = candid({canister_id = p, …}), …}, p) is_effective_canister_id(Request {canister_id = p, …}, p), if p ≠ ic_principal ``` #### 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. @@ -3523,25 +3684,15 @@ 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]); + memory_usage_snapshot = memory_usage_snapshot(S.snapshots[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;} ) @@ -3660,19 +3811,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: @@ -3708,19 +3847,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) ``` @@ -3760,19 +3887,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) ``` @@ -3813,19 +3928,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) ``` @@ -3885,6 +3988,8 @@ 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]); + memory_usage_snapshot = memory_usage_snapshot(S.snapshots[M.receiver]); freezing_threshold = S.freezing_threshold[M.receiver]; subnet_size = S.canister_subnet[M.receiver].subnet_size; certificate = NoCertificate; @@ -3938,7 +4043,7 @@ if res.cycles_accepted ≤ Available (res.cycles_used + ∑ [ MAX_CYCLES_PER_RESPONSE + call.transferred_cycles | call ∈ res.new_calls ]) ≤ (S.balances[M.receiver] + res.cycles_accepted + (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE)) - Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_state) + Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], S.snapshots[A.canister_id], New_state) New_balance = (S.balances[M.receiver] + res.cycles_accepted + (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE)) - (res.cycles_used + ∑ [ MAX_CYCLES_PER_RESPONSE + call.transferred_cycles | call ∈ res.new_calls ]) @@ -3950,7 +4055,9 @@ 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]) + + memory_usage_snapshot(S.snapshots[M.receiver]), S.canister_subnet[M.receiver].subnet_size, ) New_reserved_balance ≤ S.reserved_balance_limits[M.receiver] @@ -3961,7 +4068,9 @@ 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]) + + memory_usage_snapshot(S.snapshots[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 @@ -4201,23 +4310,12 @@ if A.settings.wasm_memory_limit is not null: else: New_wasm_memory_limit = 0 -Cycles_reserved = cycles_to_reserve(S, Canister_id, New_compute_allocation, New_memory_allocation, EmptyCanister.wasm_state) +Cycles_reserved = cycles_to_reserve(S, Canister_id, New_compute_allocation, New_memory_allocation, null, EmptyCanister.wasm_state) 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 @@ -4242,8 +4340,9 @@ State after ```html -S with +S' = S with canisters[Canister_id] = EmptyCanister + snapshots[A.canister_id] = null time[Canister_id] = CurrentTime global_timer[Canister_id] = 0 controllers[Canister_id] = New_controllers @@ -4309,7 +4408,8 @@ M.caller ∈ S.controllers[A.canister_id] if New_memory_allocation > 0: 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) ≤ New_memory_allocation + memory_usage_canister_history(New_canister_history) + + memory_usage_snapshot(S.snapshots[A.canister_id]) ≤ New_memory_allocation if A.settings.compute_allocation is not null: New_compute_allocation = A.settings.compute_allocation @@ -4332,24 +4432,12 @@ if A.settings.wasm_memory_limit is not null: else: New_wasm_memory_limit = S.wasm_memory_limit[A.canister_id] -Cycles_reserved = cycles_to_reserve(S, A.canister_id, New_compute_allocation, New_memory_allocation, S.canisters[A.canister_id].wasm_state) +Cycles_reserved = cycles_to_reserve(S, A.canister_id, New_compute_allocation, New_memory_allocation, S.snapshots[A.canister_id], S.canisters[A.canister_id].wasm_state) 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; @@ -4376,7 +4464,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 @@ -4450,7 +4538,9 @@ 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]) + + memory_usage_snapshot(S.snapshots[A.canister_id]), S.freezing_threshold[A.canister_id], S.canister_subnet[A.canister_id].subnet_size, ); @@ -4639,6 +4729,8 @@ 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); + memory_usage_snapshot = memory_usage_snapshot(S.snapshots[A.canister_id]); freezing_threshold = S.freezing_threshold[A.canister_id]; subnet_size = S.canister_subnet[A.canister_id].subnet_size; certificate = NoCertificate; @@ -4646,43 +4738,21 @@ Env = { canister_version = S.canister_version[A.canister_id] + 1; } Mod.init(A.canister_id, A.arg, M.caller, Env) = Return {new_state = New_state; new_certified_data = New_certified_data; new_global_timer = New_global_timer; cycles_used = Cycles_used;} -Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_state) +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], S.snapshots[A.canister_id], New_state) 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) + + memory_usage_snapshot(S.snapshots[A.canister_id]) ≤ 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] @@ -4709,7 +4779,7 @@ State after ```html -S with +S' = S with canisters[A.canister_id] = { wasm_state = New_state; module = Mod; @@ -4770,6 +4840,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]; @@ -4777,10 +4848,13 @@ 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]); + memory_usage_snapshot = memory_usage_snapshot(S.snapshots[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]; } ( @@ -4826,43 +4900,21 @@ Env2 = Env with { Mod.post_upgrade(Persisted_state, A.arg, M.caller, Env2) = Return {new_state = New_state; new_certified_data = New_certified_data'; new_global_timer = New_global_timer; cycles_used = Cycles_used';} -Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_state) +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], S.snapshots[A.canister_id], New_state) New_balance = S.balances[A.canister_id] - Cycles_used - 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(S[A.canister_id].chunk_store) + + memory_usage_snapshot(S.snapshots[A.canister_id]) ≤ 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] @@ -4888,7 +4940,7 @@ State after ```html -S with +S' = S with canisters[A.canister_id] = { wasm_state = New_state; module = Mod; @@ -5265,6 +5317,7 @@ State after S with canisters[A.canister_id] = (deleted) + snapshots[A.canister_id] = (deleted) controllers[A.canister_id] = (deleted) compute_allocation[A.canister_id] = (deleted) memory_allocation[A.canister_id] = (deleted) @@ -5283,6 +5336,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 @@ -5440,7 +5494,7 @@ if A.settings.wasm_memory_limit is not null: else: New_wasm_memory_limit = 0 -Cycles_reserved = cycles_to_reserve(S, Canister_id, New_compute_allocation, New_memory_allocation, EmptyCanister.wasm_state) +Cycles_reserved = cycles_to_reserve(S, Canister_id, New_compute_allocation, New_memory_allocation, null, EmptyCanister.wasm_state) if A.amount is not null: New_balance = A.amount - Cycles_reserved else: @@ -5448,18 +5502,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 @@ -5484,8 +5527,9 @@ State after ```html -S with +S' = S with canisters[Canister_id] = EmptyCanister + snapshots[Canister_id] = null time[Canister_id] = CurrentTime global_timer[Canister_id] = 0 controllers[Canister_id] = New_controllers @@ -5546,6 +5590,206 @@ S with ``` +#### IC Management Canister: Take canister snapshot + +Only the controllers of the given canister can take a snapshot. +A snapshot will be identified internally by a system-generated opaque `Snapshot_id`. + + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ msg ∈ Older_messages. msg.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'take_canister_snapshot' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] +if A.replace_snapshot is not null: + S.snapshots[A.canister_id].snapshot_id = A.replace_snapshot +else: + S.snapshots[A.canister_id] = null + +New_snapshot = Snapshot { + snapshot_id = Snapshot_id; + wasm_state = S.canisters[A.canister_id].wasm_state; + raw_module = S.canisters[A.canister_id].raw_module; + chunk_store = S.chunk_store[A.canister_id]; + certified_data = S.certified_data[A.canister_id]; + canister_version = S.canister_version[A.canister_id]; + take_at_timestamp = S.time[A.canister_id]; +} +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshot, S.canisters[A.canister_id]) +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, A.canister_id) ≥ 0 +``` + +State after + +```html + +S' = S with + snapshots[A.canister_id] = New_snapshot + balances[A.canister_id] = New_balance + reserved_balances[A.canister_id] = New_reserved_balance + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin; + response = Reply (candid({ + id = Snapshot_id; + taken_at_timestamp = S.time[A.canister_id]; + total_size = memory_usage_snapshot(New_snapshot); + })); + refunded_cycles = M.transferred_cycles; + } + +``` + + +#### IC Management Canister: Load canister snapshot + + +Only the controllers of the given canister can load a snapshot. + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ msg ∈ Older_messages. msg.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'load_canister_snapshot' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] +S.snapshots[A.canister_id].snapshot_id = A.snapshot_id + +New_state = { + wasm_state = S.snapshots[A.canister_id].wasm_state; + raw_module = S.snapshots[A.canister_id].raw_module; + module = parse_wasm_mod(S.snapshots[A.canister_id].raw_module); + public_custom_sections = parse_public_custom_sections(S.snapshots[A.canister_id].raw_module); + private_custom_sections = parse_private_custom_sections(S.snapshots[A.canister_id].raw_module); +} +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], S.snapshots[A.canister_id], New_state) +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] + +S.canister_history[A.canister_id] = { + total_num_changes = N; + recent_changes = H; +} +New_canister_history = { + total_num_changes = N + 1; + recent_changes = H · { + timestamp_nanos = S.time[A.canister_id]; + canister_version = S.canister_version[A.canister_id] + 1 + origin = change_origin(M.caller, A.sender_canister_version, M.origin); + details = LoadSnapshot { + snapshot_id = S.snapshots[A.canister_id].snapshot_id + canister_version = S.snapshots[A.canister_id].canister_version + taken_at_timestamp = S.snapshots[A.canister_id].take_at_timestamp + }; + }; +} + +liquid_balance(S', A.canister_id) ≥ 0 + +if S.memory_allocation[A.canister_id] > 0: + memory_usage_wasm_state(New_state.wasm_state) + + memory_usage_raw_module(New_state.raw_module) + + memory_usage_canister_history(New_canister_history) + + memory_usage_chunk_store(S.chunk_store[A.canister_id]) + + memory_usage_snapshot(S.snapshots[A.canister_id]) ≤ S.memory_allocation[A.canister_id] + +``` + +State after + +```html + +S' = S with + canisters[A.canister_id] = New_state + chunk_store[A.canister_id] = S.snapshots[A.canister_id].chunk_store + certified_data[A.canister_id] = S.snapshots[A.canister_id].certified_data + balances[A.canister_id] = New_balance + reserved_balances[A.canister_id] = New_reserved_balance + canister_history[A.canister_id] = New_canister_history + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin; + response = Reply (candid()); + refunded_cycles = M.transferred_cycles; + } + +``` + +#### IC Management Canister: List canister snapshots + +Only the controllers of the given canister can get a list of the existing snapshots. + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ msg ∈ Older_messages. msg.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'list_canister_snapshots' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] + +Snapshots = if S.snapshots[A.canister_id] is null then [] else [{ + id = S.snapshots[A.canister_id].snapshot_id; + taken_at_timestamp = S.snapshots[A.canister_id].taken_at_timestamp; + total_size = memory_usage_snapshot(S.snapshots[A.canister_id]); + }] + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid(Snapshots)) + refunded_cycles = M.transferred_cycles + } + +``` +#### IC Management Canister: Delete canister snapshot + +A snapshot may be deleted only by the controllers of the canister for which the snapshot was taken. + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ msg ∈ Older_messages. msg.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'delete_canister_snapshot' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] +S.snapshots[A.canister_id].snapshot_id = A.snapshot_id + +``` + +State after + +```html + +S with + S.snapshots[A.canister_id] = null + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid()); + refunded_cycles = M.transferred_cycles + } + +``` + #### Callback invocation When an inter-canister call has been responded to, we can queue the call to the callback. @@ -5704,6 +5948,7 @@ State after S with canisters[CanisterId] = EmptyCanister + snapshots[CanisterId] = null certified_data[CanisterId] = "" canister_history[CanisterId] = { total_num_changes = N; @@ -5960,6 +6205,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]; @@ -5968,6 +6214,8 @@ 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]); + memory_usage_snapshot = memory_usage_snapshot(S.snapshots[Canister_id]); freezing_threshold = S.freezing_threshold[Canister_id]; subnet_size = S.canister_subnet[Canister_id].subnet_size; certificate = Cert; @@ -5981,19 +6229,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) @@ -6046,7 +6282,7 @@ composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Canister_id, Return (Response, Cycles, S) else Return (Reject (CANISTER_ERROR, ), Cycles, S) - ``` +``` Submitted request `E` @@ -6059,7 +6295,7 @@ E.content = CanisterQuery Q Q.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) |Q.nonce| <= 32 is_effective_canister_id(E.content, ECID) -S.system_time <= Q.ingress_expiry +S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id ``` @@ -6121,7 +6357,7 @@ Conditions E.content = ReadState RS TS = verify_envelope(E, RS.sender, S.system_time) |E.content.nonce| <= 32 -S.system_time <= RS.ingress_expiry +S.system_time <= RS.ingress_expiry or RS.sender = anonymous_id ∀ path ∈ RS.paths. may_read_path_for_canister(S, R.sender, path) ∀ (["request_status", Rid] · _) ∈ RS.paths. ∀ R ∈ dom(S.requests). hash_of_map(R) = Rid => R.canister_id ∈ TS @@ -6299,6 +6535,26 @@ 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.memory_usage_snapshot, + es.params.sysenv.subnet_size, + ) + ) +``` + - For more convenience when creating a new `ExecutionState`, we define the following partial records: ``` empty_params = { @@ -6847,20 +7103,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()) @@ -6949,36 +7192,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 @@ -6987,25 +7210,13 @@ ic0.call_peform() : ( err_code : i32 ) = if es.context ∉ {U, CQ, Ry, Rt, CRy, CRt, T} then Trap {cycles_used = es.cycles_used;} if es.pending_call = NoPendingCall then Trap {cycles_used = es.cycles_used;} - es.balance := es.balance - MAX_CYCLES_PER_RESPONSE - - // 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() + // `system_cannot_do_this_call_now` abstracts over resource issues preventing the call from being made + if liquid_balance(es) < MAX_CYCLES_PER_RESPONSE or system_cannot_do_this_call_now() then discard_pending_call() return or + es.balance := es.balance - MAX_CYCLES_PER_RESPONSE es.calls := es.calls · es.pending_call es.pending_call := NoPendingCall return 0 @@ -7013,7 +7224,7 @@ ic0.call_peform() : ( err_code : i32 ) = // helper function discard_pending_call() = if es.pending_call ≠ NoPendingCall then - es.balance := es.balance + MAX_CYCLES_PER_RESPONSE + es.pending_call.transferred_cycles + es.balance := es.balance + es.pending_call.transferred_cycles es.pending_call := NoPendingCall ic0.stable_size() : (page_count : i32) =