diff --git a/.docs-plan/decisions.md b/.docs-plan/decisions.md index 36c5811..ccd21c4 100644 --- a/.docs-plan/decisions.md +++ b/.docs-plan/decisions.md @@ -15,17 +15,17 @@ Record decisions that constrain future work — things an agent needs to know th ## 2026-04-27: ICRC standards reference restructured into index + detail pages -**Context:** `reference/token-standards.md` mixed two unrelated standard families (digital asset standards ICRC-1/2/3/7/37 and wallet signer standards ICRC-21/25/27/29/49) under a title that didn't fit either category cleanly. The page also used "token" as a primary descriptor, conflicting with the brand voice push toward "digital assets." -**Decision:** Split into two pages. `reference/icrc-standards.md` is a lightweight index of all ICRC standards grouped by category (extensible for future standards). `reference/digital-asset-standards.md` (renamed from `token-standards.md`) is the deep reference for ICRC-1/2/3/7/37 only. Wallet signer standard detail stays in the wallet integration guide; the index page links to it. `guides/digital-assets/token-ledgers.mdx` renamed to `guides/digital-assets/ledgers.mdx`. +**Context:** `references/token-standards.md` mixed two unrelated standard families (digital asset standards ICRC-1/2/3/7/37 and wallet signer standards ICRC-21/25/27/29/49) under a title that didn't fit either category cleanly. The page also used "token" as a primary descriptor, conflicting with the brand voice push toward "digital assets." +**Decision:** Split into two pages. `references/icrc-standards.md` is a lightweight index of all ICRC standards grouped by category (extensible for future standards). `references/digital-asset-standards.md` (renamed from `token-standards.md`) is the deep reference for ICRC-1/2/3/7/37 only. Wallet signer standard detail stays in the wallet integration guide; the index page links to it. `guides/digital-assets/token-ledgers.mdx` renamed to `guides/digital-assets/ledgers.mdx`. **Rationale:** "Token Standards" as a page title was inaccurate (covered signers too) and jargon-heavy. "ICRC Standards" as a single page title would be too broad (implies ALL ICRC work). Separating the index from the detail page gives a clean extensible home for future ICRC standards without forcing unrelated content together. -**When to revisit:** If wallet signer content grows enough to warrant its own `reference/signer-standards.md`, add it to the index and link from there. +**When to revisit:** If wallet signer content grows enough to warrant its own `references/signer-standards.md`, add it to the index and link from there. --- ## 2026-04-24: Developer Tools is a top-level sidebar item, not a section -**Context:** The tools overview page (`reference/developer-tools.md`) is a toolchain catalog — not a how-to guide, concept explanation, or specification. It doesn't fit cleanly in any Diataxis quadrant. It was previously under `guides/tools/` and then considered for Reference. -**Decision:** Expose it as a single flat top-level sidebar link between Concepts and Languages. The sidebar order is: Getting Started → Guides → Concepts → Developer Tools → Languages → Reference. The file lives at `docs/reference/developer-tools.md` with `sidebar: hidden: true` to suppress it from the Reference autogenerate; `sidebar.mjs` references it explicitly via `{ slug: "reference/developer-tools", label: "Developer Tools" }`. +**Context:** The tools overview page (`references/developer-tools.md`) is a toolchain catalog — not a how-to guide, concept explanation, or specification. It doesn't fit cleanly in any Diataxis quadrant. It was previously under `guides/tools/` and then considered for Reference. +**Decision:** Expose it as a single flat top-level sidebar link between Concepts and Languages. The sidebar order is: Getting Started → Guides → Concepts → Developer Tools → Languages → Reference. The file lives at `docs/references/developer-tools.md` with `sidebar: hidden: true` to suppress it from the Reference autogenerate; `sidebar.mjs` references it explicitly via `{ slug: "reference/developer-tools", label: "Developer Tools" }`. **Rationale:** A catalog page warrants top-level visibility. Placing it between Concepts and Languages follows the natural developer flow: understand the platform, know the tools, then go deep on your language. Single flat link (no collapsible) is correct while it remains one page. **When to revisit:** If the tools section grows to multiple pages (dedicated icp-cli reference, JS SDK getting-started, PocketIC advanced guide), convert to a collapsible group with `autogenerate` from a new `docs/tools/` directory and update this decision. @@ -180,7 +180,7 @@ Record decisions that constrain future work — things an agent needs to know th ## 2026-03-13: Diataxis content-type rules — no CLI commands in concept pages **Context:** PR #2 (canisters concept page) included 6 `icp` CLI command blocks in the lifecycle section. Concept pages should explain *what* and *why*, not provide step-by-step procedures. The other concept pages (network-overview, app-architecture) correctly contained zero CLI commands, but the rule was implicit. -**Decision:** Added explicit Diataxis content-type rules to both CLAUDE.md ("Content rules") and CONTRIBUTING.md ("Content types (Diataxis)"). `concepts/` pages must not contain CLI commands or step-by-step procedures — link to the relevant guide instead. `getting-started/` and `guides/` pages may include CLI commands. `reference/` pages use them sparingly for syntax examples only. +**Decision:** Added explicit Diataxis content-type rules to both CLAUDE.md ("Content rules") and CONTRIBUTING.md ("Content types (Diataxis)"). `concepts/` pages must not contain CLI commands or step-by-step procedures — link to the relevant guide instead. `getting-started/` and `guides/` pages may include CLI commands. `references/` pages use them sparingly for syntax examples only. **Rationale:** Making the rule explicit prevents future agents from mixing procedural content into explanatory pages. The Diataxis framework is already a stated design choice but the per-section implications for CLI command usage were not spelled out. ## 2026-03-13: Never link to internetcomputer.org/docs — it's being replaced @@ -282,3 +282,13 @@ Record decisions that constrain future work — things an agent needs to know th **Decision:** Switch all domain references from `beta-docs.internetcomputer.org` to `docs.internetcomputer.org`. Updated files: `astro.config.mjs` (site URL + og/twitter/schema.org meta), `public/robots.txt` (sitemap), `public/og-image.svg` (footer text), `README.md`, `AGENTS.md` (never-link rule + portal tracking section), `scripts/validate.js` (error messages). The `docs.internetcomputer.org` lint rule in validate.js is kept — it still enforces relative paths for internal links. **Rationale:** The beta domain was always a temporary staging address. With the portal retired, `docs.internetcomputer.org` is the permanent home. **Alternatives considered:** Keep beta domain as a redirect origin (handled at DNS/CDN level, not in code) + +--- + +## 2026-05-04: ic-interface-spec split into 7 focused sub-pages + +**Context:** `docs/references/ic-interface-spec.md` was a 483K monolith covering 13 distinct sections serving very different audiences (agent builders, CDK developers, canister developers, protocol implementors). The `afdocs` checker flagged it for both `page-size-markdown` (482K chars, limit 480K) and `page-size-html` (524K converted, 79% boilerplate). The Abstract behavior section alone was 5,747 lines (formal state machine notation) accounting for 64% of the file. +**Decision:** Split into `docs/references/ic-interface-spec/` with 7 pages: `index.md` (intro, pervasive concepts, system state tree), `https-interface.md`, `canister-interface.md` (module format + System API), `management-canister.md` (management + Bitcoin + provisional APIs), `certification.md` (certification + HTTP Gateway), `abstract-behavior.md`, `changelog.md`. All 204 heading anchors were remapped; cross-file `(#anchor)` references updated to `(./target.md#anchor)`. The Abstract behavior page carries a note directing application developers to the practical sections. CLAUDE.md sync rules updated with a section-to-file mapping table for portal bump workflow. +**Rationale:** Each section serves a distinct audience and use case. Splitting enables independent navigation, brings all pages under the 480K size limit, and gives the agent-friendly docs checker clean per-section `.md` endpoints. The technical-documentation skill confirmed the split is correct Diataxis structure for a reference spec of this scope. +**Sync:** Portal bump workflow changed from patch-on-single-file to inspect-diff-and-apply-by-section. See CLAUDE.md "Step 2 — ic-interface-spec/" checklist. +**When to revisit:** If the portal is fully retired as source, remove the portal sync checklist from CLAUDE.md and mark these files as hand-maintained. diff --git a/AGENTS.md b/AGENTS.md index 22d4fd1..de9a920 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,7 +119,7 @@ git checkout main ## Ask first (confirm with the user before doing these) -- Creating new top-level sections (getting-started, guides, concepts, languages, reference) +- Creating new top-level sections (getting-started, guides, concepts, languages, references) - Adding new pages not in the migration plan - Removing existing pages from the structure - Changing a page's sync recommendation from hand-written to synced (or vice versa) @@ -145,7 +145,7 @@ git checkout main - Link to internal pages that don't exist — every `[text](path.md)` must resolve to an actual file. Run `ls ` before linking. Links to `.mdx` pages use `.md` extension (Astro resolves both). - Link externally when an internal page exists — check `docs/` before using an external URL - Offer, suggest, or perform PR reviews unless a human explicitly asks -- Write em-dashes (`—`) or use `--` as an em-dash substitute in prose. These are banned in all content: body text, bullet descriptions, "Further reading" links, and inline comments. Use a colon, semicolon, period, comma, or parentheses instead. (`--` is only acceptable inside fenced code blocks as a code comment or CLI flag.) +- Write em-dashes (`—`) or use `--` as an em-dash substitute in prose. These are banned in all content: body text, bullet descriptions, link label text (including "Next steps", "Further reading", and "See also" sections), and inline comments. Use a colon, semicolon, period, comma, or parentheses instead. (`--` is only acceptable inside fenced code blocks as a code comment or CLI flag.) - Rename Candid field names, management canister API identifiers, or example/repository names to satisfy jargon rules — these are protocol-level identifiers that must match the actual interface (e.g. `dapps`, `RegisterDappCanisters`, `encrypted-notes-dapp-vetkd`) - Remove domain-specific technical terms that are standard vocabulary in their context: "DeFi" and "smart contract" in DeFi/token guides, "DAO" and "decentralized autonomous organization" in governance guides, "smart contracts on other chains" in chain fusion guides. These terms must stay because the target audience uses them and alternatives would be less precise. @@ -182,7 +182,7 @@ docs/ # All documentation (.md only) — src/content/docs/ │ └── tools/ # Developer tools ├── concepts/ # Explanations ├── languages/ # Language-specific (Motoko synced, Rust hand-written) -└── reference/ # Specifications and reference +└── references/ # Specifications and reference ``` ## Source material repos (`.sources/`) @@ -290,18 +290,27 @@ EOF | `candid` | Check for spec changes affecting the Candid reference or type-mapping examples | | `response-verification` | Check for API changes affecting certified variables patterns | | `dotskills` | Check if the `technical-documentation` skill changed in ways that affect review criteria | -| `internetidentity` | Check for spec changes in `docs/ii-spec.mdx`; re-sync `internet-identity-spec.md`. Re-copy Candid interface from `src/internet_identity/internet_identity.did` if changed | +| `internetidentity` | Check for spec changes in `docs/ii-spec.mdx`; re-sync `internet-identity-spec.md` (see link adaptation note below). Re-copy Candid interface from `src/internet_identity/internet_identity.did` if changed | | `chain-fusion-signer` | Check for changed canister IDs, API methods, or key derivation patterns | | `papi` | Check for changed payment interface or cycle cost model | | `ic-pub-key` | Check for changed CLI flags or commands | +**Link adaptation for `internet-identity-spec.md`:** The upstream source (`docs/ii-spec.mdx`) uses absolute `internetcomputer.org` URLs pointing to the IC interface spec. Our file uses relative paths into the split `ic-interface-spec/` directory. After every re-sync, run: +```bash +grep -n "ic-interface-spec" docs/references/internet-identity-spec.md +``` +Any link of the form `internetcomputer.org/.../ic-interface-spec#` or `./ic-interface-spec.md#` must be converted. Use the anchor-to-file mapping at the bottom of `docs/references/internet-identity-spec.md` as the authoritative guide. If a new anchor appears that is not in the comment, find its file with: +```bash +grep -r "{#}" docs/references/ic-interface-spec/ +``` + ### Synced files from submodules | Local file | Source | Affects | |-----------|--------|---------| -| `public/reference/ic.did` | `.sources/portal/docs/references/_attachments/ic.did` | Management canister reference — new/changed methods require updating `docs/reference/management-canister.md` | -| `docs/reference/ic-interface-spec.md` | `.sources/portal/docs/references/ic-interface-spec.md` | Full IC interface spec — apply portal diff as a patch on every bump | -| `docs/reference/http-gateway-spec.md` | `.sources/portal/docs/references/http-gateway-protocol-spec.md` | HTTP Gateway spec — apply portal diff as a patch on every bump | +| `public/reference/ic.did` | `.sources/portal/docs/references/_attachments/ic.did` | Management canister reference — new/changed methods require updating `docs/references/management-canister.md` | +| `docs/references/ic-interface-spec/` | `.sources/portal/docs/references/ic-interface-spec.md` | IC interface spec split into 7 focused pages — apply portal diffs by section (see checklist below) | +| `docs/references/http-gateway-spec.md` | `.sources/portal/docs/references/http-gateway-protocol-spec.md` | HTTP Gateway spec — apply portal diff as a patch on every bump | **Portal bump checklist (run on every portal bump):** @@ -309,22 +318,47 @@ EOF 1. `diff public/reference/ic.did .sources/portal/docs/references/_attachments/ic.did` 2. If changed: `cp .sources/portal/docs/references/_attachments/ic.did public/reference/ic.did` 3. Review diff for new/changed/removed methods -4. Update `docs/reference/management-canister.md` and any affected guides +4. Update `docs/references/management-canister.md` and any affected guides -**Step 2 — `ic-interface-spec.md`:** For every commit in the bump range that touched `docs/references/ic-interface-spec.md`: -1. `git -C .sources/portal show -- docs/references/ic-interface-spec.md > /tmp/patch.diff` -2. `patch -F 5 -p1 --input=/tmp/patch.diff docs/reference/ic-interface-spec.md` -3. Resolve any rejects manually (our file has intentional diffs: Astro frontmatter, internal link fixes) -4. Verify new methods/fields are reflected in `docs/reference/management-canister.md` if they touch the management canister +**Step 2 — `ic-interface-spec/`:** The spec is now split into 7 files under `docs/references/ic-interface-spec/`. Each file maps to a section of the portal source: + +| File | Portal section (## heading) | +|---|---| +| `index.md` | Introduction, Pervasive concepts, The system state tree | +| `https-interface.md` | HTTPS Interface | +| `canister-interface.md` | Canister module format, Canister interface (System API) | +| `management-canister.md` | The IC management canister, The IC Bitcoin API, The IC Provisional API | +| `certification.md` | Certification, The HTTP Gateway protocol | +| `abstract-behavior.md` | Abstract behavior | +| `changelog.md` | `.sources/portal/docs/references/_attachments/interface-spec-changelog.md` (NOT `ic-interface-spec.md`) | + +For every commit in the bump range that touched `docs/references/ic-interface-spec.md`: +1. `git -C .sources/portal show -- docs/references/ic-interface-spec.md > /tmp/spec.diff` +2. Inspect the diff: identify which section(s) changed +3. Apply the relevant hunks manually to the corresponding file(s) in `docs/references/ic-interface-spec/` +4. Update any cross-file anchor links (`(./other.md#anchor)`) if headings were added or removed +5. Verify new methods/fields are reflected in `docs/references/management-canister.md` if they touch the management canister + +For every commit in the bump range that touched `docs/references/_attachments/interface-spec-changelog.md`: +1. `git -C .sources/portal show -- docs/references/_attachments/interface-spec-changelog.md > /tmp/changelog.diff` +2. Apply the new version entries to `docs/references/ic-interface-spec/changelog.md` **Step 3 — `http-gateway-spec.md`:** For every commit in the bump range that touched `docs/references/http-gateway-protocol-spec.md`: 1. `git -C .sources/portal show -- docs/references/http-gateway-protocol-spec.md > /tmp/patch.diff` -2. `patch -F 5 -p1 --input=/tmp/patch.diff docs/reference/http-gateway-spec.md` -3. Resolve any rejects manually +2. `patch -F 5 -p1 --input=/tmp/patch.diff docs/references/http-gateway-spec.md` +3. Resolve any rejects manually (see note below on link adaptation) +4. Run `grep -n "ic-interface-spec" docs/references/http-gateway-spec.md` and convert any newly introduced links + +**Link adaptation for `http-gateway-spec.md`:** The portal source uses absolute `/references/ic-interface-spec#anchor` URLs. Our file uses relative paths into the split `ic-interface-spec/` directory. After every sync, any link of the form `/references/ic-interface-spec#` or `./ic-interface-spec.md#` must be converted. Use the anchor-to-file mapping at the bottom of `docs/references/http-gateway-spec.md` as the authoritative guide. If a new anchor appears that is not in the comment, find its file with: +```bash +grep -r "{#}" docs/references/ic-interface-spec/ +``` **Finding which commits touched which files:** ```bash git -C .sources/portal log --oneline .. -- docs/references/ic-interface-spec.md +git -C .sources/portal show -- docs/references/ic-interface-spec.md | grep "^[+-]## " | head -20 # identify which sections changed +git -C .sources/portal log --oneline .. -- docs/references/_attachments/interface-spec-changelog.md git -C .sources/portal log --oneline .. -- docs/references/http-gateway-protocol-spec.md git -C .sources/portal log --oneline .. -- docs/references/_attachments/ic.did ``` diff --git a/astro.config.mjs b/astro.config.mjs index fe81e05..13324ca 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -108,7 +108,7 @@ export default defineConfig({ }, { tag: "script", - content: `document.addEventListener('DOMContentLoaded',()=>{document.querySelectorAll('a[href^="http"]').forEach(a=>{a.setAttribute('target','_blank');a.setAttribute('rel','noopener noreferrer')});document.querySelectorAll('[data-copy]').forEach(b=>{b.addEventListener('click',()=>{navigator.clipboard.writeText(b.dataset.copy);const i=b.querySelector('svg');if(i){const orig=i.innerHTML;i.innerHTML='';i.style.stroke='#22c55e';i.style.opacity='1';setTimeout(()=>{i.innerHTML=orig;i.style.stroke='';i.style.opacity=''},1500)}})});const sb=document.getElementById('skills-give-btn');const sl=document.getElementById('skills-give-label');if(sb&&sl){const orig=sl.textContent;sb.addEventListener('click',()=>{navigator.clipboard.writeText('Fetch https://skills.internetcomputer.org/llms.txt and follow its instructions when building on ICP').catch(()=>{});sl.textContent='Now paste into your agent';setTimeout(()=>{sl.textContent=orig},3000)})}})`, + content: `document.addEventListener('DOMContentLoaded',()=>{document.querySelectorAll('a[href^="http://"],a[href^="https://"]').forEach(a=>{a.setAttribute('target','_blank');a.setAttribute('rel','noopener noreferrer')});document.querySelectorAll('[data-copy]').forEach(b=>{b.addEventListener('click',()=>{navigator.clipboard.writeText(b.dataset.copy);const i=b.querySelector('svg');if(i){const orig=i.innerHTML;i.innerHTML='';i.style.stroke='#22c55e';i.style.opacity='1';setTimeout(()=>{i.innerHTML=orig;i.style.stroke='';i.style.opacity=''},1500)}})});const sb=document.getElementById('skills-give-btn');const sl=document.getElementById('skills-give-label');if(sb&&sl){const orig=sl.textContent;sb.addEventListener('click',()=>{navigator.clipboard.writeText('Fetch https://skills.internetcomputer.org/llms.txt and follow its instructions when building on ICP').catch(()=>{});sl.textContent='Now paste into your agent';setTimeout(()=>{sl.textContent=orig},3000)})}})`, }, ], customCss: [ diff --git a/docs/404.mdx b/docs/404.mdx index d61a973..a0f159d 100644 --- a/docs/404.mdx +++ b/docs/404.mdx @@ -23,6 +23,6 @@ Pick a guide that matches what you're building. - **[Quickstart](/getting-started/quickstart/)**: Build and deploy your first canister in minutes. - **[Concepts](/concepts/)**: Explanations of ICP architecture and design decisions. - **[Languages](/languages/)**: Language-specific guides for Rust and Motoko. -- **[Reference](/reference/)**: Specifications, canister IDs, cycle costs, and glossary. +- **[References](/references/)**: Specifications, canister IDs, cycle costs, and glossary. You can also use the search bar at the top of the page to find what you need. diff --git a/docs/concepts/canisters.md b/docs/concepts/canisters.md index ef6b849..aaa369c 100644 --- a/docs/concepts/canisters.md +++ b/docs/concepts/canisters.md @@ -62,7 +62,7 @@ Each canister has two storage regions: **Heap memory** is standard Wasm linear memory. It holds your program's heap-allocated data: variables, data structures, and anything your code allocates at runtime. Both 32-bit and 64-bit Wasm memory are supported. Heap memory is cleared when you upgrade the canister's Wasm module. -**Stable memory** is a separate address space accessed through the [system API](../reference/ic-interface-spec.md). It survives upgrades, making it the right place for any data that must persist long-term. Libraries like `StableBTreeMap` (Rust) or the [`core`](https://mops.one/core/docs) persistent data structures (Motoko) let you work with stable memory through familiar abstractions. +**Stable memory** is a separate address space accessed through the [system API](../references/ic-interface-spec/canister-interface.md). It survives upgrades, making it the right place for any data that must persist long-term. Libraries like `StableBTreeMap` (Rust) or the [`core`](https://mops.one/core/docs) persistent data structures (Motoko) let you work with stable memory through familiar abstractions. After a message executes successfully, the system atomically commits all memory changes. If execution traps (fails), no changes are committed. The canister's state rolls back to what it was before that message. diff --git a/docs/concepts/chain-key-cryptography.md b/docs/concepts/chain-key-cryptography.md index b1a8348..5f4370b 100644 --- a/docs/concepts/chain-key-cryptography.md +++ b/docs/concepts/chain-key-cryptography.md @@ -52,7 +52,7 @@ Each scheme is backed by a pair of management canister methods: - **Public key retrieval** (`ecdsa_public_key`, `schnorr_public_key`): returns a canister's public key for a given derivation path. - **Signing** (`sign_with_ecdsa`, `sign_with_schnorr`): computes a threshold signature using the canister's derived key. -See the [Management canister reference](../reference/management-canister.md) for the full API, and the [IC interface specification](../reference/ic-interface-spec.md) for the authoritative protocol-level details. +See the [Management canister reference](../references/management-canister.md) for the full API, and the [IC interface specification](../references/ic-interface-spec/index.md) for the authoritative protocol-level details. ### Key derivation @@ -87,7 +87,7 @@ The following master keys are deployed at the time of writing. The NNS can add n Test keys are available for development and run on smaller subnets with lower signing costs. They should not be used for anything of value. Production keys run on high-replication subnets (34+ nodes) for stronger security guarantees. Each key is also reshared to a backup subnet for availability: if the signing subnet fails, the backup can take over without generating a new key. -For signing costs, see [Cycles costs](../reference/cycles-costs.md). +For signing costs, see [Cycles costs](../references/cycles-costs.md). ## Supported chains @@ -106,6 +106,6 @@ For more on how upgrades work at the protocol level, see the [Chain Evolution](h - [Chain Fusion](chain-fusion.md): how canisters use chain-key signatures to interact with other blockchains - [Ethereum integration](../guides/chain-fusion/ethereum.md): using threshold ECDSA with Ethereum and EVM chains - [VetKeys](vetkeys.md): a related cryptographic primitive for onchain encryption -- [Management canister reference](../reference/management-canister.md): the threshold signing API +- [Management canister reference](../references/management-canister.md): the threshold signing API diff --git a/docs/concepts/cycles.md b/docs/concepts/cycles.md index db8a6b7..58aae02 100644 --- a/docs/concepts/cycles.md +++ b/docs/concepts/cycles.md @@ -83,7 +83,7 @@ The tradeoff is that developers must forecast and fund usage upfront rather than ## Related - [Cycles Management](../guides/canister-management/cycles-management.md): how to check balances, top up canisters, and set freezing thresholds -- [Cycles Costs Reference](../reference/cycles-costs.md): exact cost tables for all operations +- [Cycles Costs Reference](../references/cycles-costs.md): exact cost tables for all operations - [Canisters](./canisters.md): canisters as the paying entity for compute and storage diff --git a/docs/concepts/governance.md b/docs/concepts/governance.md index da6ad52..ad36b19 100644 --- a/docs/concepts/governance.md +++ b/docs/concepts/governance.md @@ -21,7 +21,7 @@ Decisions made through the NNS include: - Authorizing new node providers and their hardware - Creating new SNS DAOs for apps -The NNS governance canister (`rrkah-fqaaa-aaaaa-aaaaq-cai`) is the entry point for all proposal submissions and voting. See [system canisters](../reference/system-canisters.md) for the full list of NNS canister IDs. +The NNS governance canister (`rrkah-fqaaa-aaaaa-aaaaq-cai`) is the entry point for all proposal submissions and voting. See [system canisters](../references/system-canisters.md) for the full list of NNS canister IDs. ## ICP tokens and the ledger @@ -70,7 +70,7 @@ An NNS proposal is a governance action submitted by a neuron and voted on by the - *UpdateCanisterSettings* for NNS canisters: Can change the behavior of system canisters. - *CreateServiceNervousSystem*: Authorizes a new SNS DAO, launching the decentralization process for an app. -See [system canisters](../reference/system-canisters.md) for the full list of NNS proposal topics and types. +See [system canisters](../references/system-canisters.md) for the full list of NNS proposal topics and types. ## Voting rewards @@ -147,6 +147,6 @@ Developers preparing for an SNS launch should ensure their codebase is stable, o - [Launch an SNS](../guides/governance/launching.md): step-by-step guide to decentralizing your app - [Manage a live SNS](../guides/governance/managing.md): proposals, upgrades, and treasury management after launch -- [System canisters reference](../reference/system-canisters.md): NNS canister IDs and interfaces +- [System canisters reference](../references/system-canisters.md): NNS canister IDs and interfaces diff --git a/docs/concepts/https-outcalls.md b/docs/concepts/https-outcalls.md index 69a7b80..e34c663 100644 --- a/docs/concepts/https-outcalls.md +++ b/docs/concepts/https-outcalls.md @@ -5,15 +5,15 @@ sidebar: order: 8 --- -Canisters on the Internet Computer can make HTTP requests to any public web server — fetching API data, posting to webhooks, or querying external services — without relying on oracles or other intermediaries. This capability is called **HTTPS outcalls**. +Canisters on the Internet Computer can make HTTP requests to any public web server (fetching API data, posting to webhooks, or querying external services) without relying on oracles or other intermediaries. This capability is called **HTTPS outcalls**. -ICP runs every canister on a subnet where all replicas execute the same code independently and must reach consensus. Outbound HTTP requests are non-trivial in this model: each replica independently contacts the server and typically receives a slightly different response — timestamps, headers, or field ordering vary — which would cause replicas to diverge. The traditional workaround is **oracles**: third-party services that fetch external data and relay it to the network, at the cost of extra complexity, fees, and a trust assumption. HTTPS outcalls solve the problem directly: the subnet reaches consensus over the response internally, so canisters call external APIs without a middleman. +ICP runs every canister on a subnet where all replicas execute the same code independently and must reach consensus. Outbound HTTP requests are non-trivial in this model: each replica independently contacts the server and typically receives a slightly different response: timestamps, headers, or field ordering vary, which would cause replicas to diverge. The traditional workaround is **oracles**: third-party services that fetch external data and relay it to the network, at the cost of extra complexity, fees, and a trust assumption. HTTPS outcalls solve the problem directly: the subnet reaches consensus over the response internally, so canisters call external APIs without a middleman. ## Replicated and non-replicated mode HTTPS outcalls have two modes controlled by the `is_replicated` field: -**Replicated mode** (default) is what the consensus mechanism below describes: all replicas independently fetch the URL, a transform function normalizes the responses, and the subnet agrees on a single result. This provides the strongest integrity guarantee — the response is confirmed by a supermajority of nodes, making it extremely difficult for any single party to tamper with it. The tradeoff is that all replicas (typically 13) send the same request to the external server within milliseconds of each other, which can trigger API rate limits. +**Replicated mode** (default) is what the consensus mechanism below describes: all replicas independently fetch the URL, a transform function normalizes the responses, and the subnet agrees on a single result. This provides the strongest integrity guarantee: the response is confirmed by a supermajority of nodes, making it extremely difficult for any single party to tamper with it. The tradeoff is that all replicas (typically 13) send the same request to the external server within milliseconds of each other, which can trigger API rate limits. **Non-replicated mode** (`is_replicated = false`) has a single replica make the request. No consensus is needed, so there is no transform function requirement and no rate-limit pressure on the external server. The tradeoff is trust: the single replica that handles the request could theoretically observe or modify the response before returning it to the canister. This mode is appropriate when the endpoint is idempotent, rate limits are a concern, or you're making POST requests where duplicate submissions would cause problems. @@ -76,7 +76,7 @@ The cost depends on two factors: If you omit `max_response_bytes`, the system assumes the maximum of 2 MB and charges accordingly: roughly 21.5 billion cycles on a 13-node subnet. Always set this to a reasonable upper bound for your expected response to avoid overpaying. Unused cycles are refunded. -For exact pricing formulas, see the [cycles costs reference](../reference/cycles-costs.md). +For exact pricing formulas, see the [cycles costs reference](../references/cycles-costs.md). ## Limitations @@ -110,7 +110,7 @@ One extension is under consideration that may affect architecture decisions: - [HTTPS outcalls guide](../guides/backends/https-outcalls.md): practical how-to with code examples in Motoko and Rust - [Chain Fusion: Ethereum integration](../guides/chain-fusion/ethereum.md): uses HTTPS outcalls via the EVM RPC canister -- [Cycles costs reference](../reference/cycles-costs.md): detailed pricing formulas +- [Cycles costs reference](../references/cycles-costs.md): detailed pricing formulas - [Learn Hub: HTTPS Outcalls](https://learn.internetcomputer.org/hc/en-us/articles/34211194553492): additional learning material diff --git a/docs/concepts/network-overview.md b/docs/concepts/network-overview.md index 150a33e..1100c29 100644 --- a/docs/concepts/network-overview.md +++ b/docs/concepts/network-overview.md @@ -22,7 +22,7 @@ When you deploy a canister, it lands on one subnet and is replicated across ever - **Shared storage budget.** All canisters on a subnet share a common storage budget. Each canister can use up to 500 GiB of stable memory, but the total available depends on the subnet's current utilization. Storage-heavy applications should consider subnet selection. - **Geographic distribution.** Nodes within a subnet are distributed across data centers, operators, and jurisdictions to maximize decentralization. Localized subnets also exist for applications with data residency requirements. -For details on subnet types and how to choose one, see [Subnet types](../reference/subnet-types.md) and [Subnet selection](../guides/canister-management/subnet-selection.md). +For details on subnet types and how to choose one, see [Subnet types](../references/subnet-types.md) and [Subnet selection](../guides/canister-management/subnet-selection.md). ## Nodes @@ -87,6 +87,6 @@ Individual applications can also be governed by a **Service Nervous System (SNS) - [Canisters](canisters.md): what runs on the network - [App architecture](app-architecture.md): how applications use subnets and canisters -- [Subnet types](../reference/subnet-types.md): comparing subnet sizes and properties +- [Subnet types](../references/subnet-types.md): comparing subnet sizes and properties diff --git a/docs/concepts/timers.md b/docs/concepts/timers.md index 2afd3f7..0e9d887 100644 --- a/docs/concepts/timers.md +++ b/docs/concepts/timers.md @@ -13,7 +13,7 @@ At the protocol level, each canister has a single **global timer**: a nanosecond Setting the timer is done through the `ic0.global_timer_set()` system API call, which takes an absolute timestamp in nanoseconds since the Unix epoch. This is the only mechanism the protocol provides directly. It is intentionally minimal: one timer, one callback, absolute time only. -The IC interface specification defines this behavior in the [timer section](../reference/ic-interface-spec.md). +The IC interface specification defines this behavior in the [timer section](../references/ic-interface-spec/canister-interface.md#global-timer). ## CDK timers libraries @@ -93,6 +93,6 @@ Timers introduce two security-relevant properties developers should understand: - [Timers guide](../guides/backends/timers.md): practical API usage for Rust and Motoko - [Canisters](canisters.md): the canister execution model -- [IC interface specification](../reference/ic-interface-spec.md): the protocol-level timer definition +- [IC interface specification](../references/ic-interface-spec/index.md): the protocol-level timer definition diff --git a/docs/concepts/verifiable-randomness.md b/docs/concepts/verifiable-randomness.md index 0748bec..2b80185 100644 --- a/docs/concepts/verifiable-randomness.md +++ b/docs/concepts/verifiable-randomness.md @@ -64,7 +64,7 @@ For applications that need verifiable randomness tied to a specific user or even ## Next steps - [Verifiable randomness guide](../guides/backends/randomness.md): how to call `raw_rand` and derive typed values in Motoko and Rust -- [Management canister reference](../reference/management-canister.md#raw_rand): `raw_rand` API specification +- [Management canister reference](../references/management-canister.md#raw_rand): `raw_rand` API specification - [Chain-key cryptography](chain-key-cryptography.md): the cryptographic foundation underlying the threshold VRF - [Security](security.md): how randomness fits into the broader ICP security model diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index abfd2be..2262ece 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -96,7 +96,7 @@ icp canister call backend greet '("World")' Output: `("Hello, World!")` -The argument `'("World")'` uses [Candid](../reference/candid-spec.md) syntax (the interface description language for the Internet Computer). The outer single quotes are shell quoting; the Candid value itself is `("World")`. You can also omit the argument and `icp canister call` will prompt you interactively. +The argument `'("World")'` uses [Candid](../references/candid-spec.md) syntax (the interface description language for the Internet Computer). The outer single quotes are shell quoting; the Candid value itself is `("World")`. You can also omit the argument and `icp canister call` will prompt you interactively. ## Stop the network @@ -110,7 +110,7 @@ icp network stop The hello-world template deploys two [canisters](../concepts/canisters.md) that run on the Internet Computer: -1. **Backend canister**: Your Motoko code compiled to WebAssembly. It exposes a `greet` function through a [Candid](../reference/candid-spec.md) interface, making it callable from any client. +1. **Backend canister**: Your Motoko code compiled to WebAssembly. It exposes a `greet` function through a [Candid](../references/candid-spec.md) interface, making it callable from any client. 2. **Frontend canister**: An asset canister that serves your React app. It automatically provides the backend's canister ID to your frontend code via a cookie, so the two canisters can communicate without manual configuration. diff --git a/docs/guides/ai-coding-agents.md b/docs/guides/ai-coding-agents.md index c4646f0..6a6c0ce 100644 --- a/docs/guides/ai-coding-agents.md +++ b/docs/guides/ai-coding-agents.md @@ -96,7 +96,7 @@ ICP skills are available without authentication: ## Next steps - [skills.internetcomputer.org](https://skills.internetcomputer.org): browse all available ICP skills -- [Developer tools](../reference/developer-tools.md): icp-cli, CDKs, and other tools in the ICP toolchain +- [Developer tools](../references/developer-tools.md): icp-cli, CDKs, and other tools in the ICP toolchain - [Quickstart](../getting-started/quickstart.md): deploy your first canister with icp-cli diff --git a/docs/guides/authentication/internet-identity.mdx b/docs/guides/authentication/internet-identity.mdx index dac345a..a3f8461 100644 --- a/docs/guides/authentication/internet-identity.mdx +++ b/docs/guides/authentication/internet-identity.mdx @@ -303,7 +303,7 @@ To keep principals consistent across your own custom domains, configure **altern The primary origin (A) does not need `derivationOrigin`: it is only required on alternative origins. -For full details, see the [Internet Identity specification](../../reference/internet-identity-spec.md). +For full details, see the [Internet Identity specification](../../references/internet-identity-spec.md). ## Common mistakes @@ -319,7 +319,7 @@ For full details, see the [Internet Identity specification](../../reference/inte - [Wallet integration](../digital-assets/wallet-integration.md) for token-based authentication alternatives - [Frontend frameworks](../frontends/frameworks.md) for framework-specific auth setup patterns -- [Internet Identity specification](../../reference/internet-identity-spec.md) for protocol details and the full alternative origins spec +- [Internet Identity specification](../../references/internet-identity-spec.md) for protocol details and the full alternative origins spec - [Security best practices](../../concepts/security.md) for identity and trust fundamentals - [AuthClient API reference](https://js.icp.build) for the full `@icp-sdk/auth` API diff --git a/docs/guides/authentication/verifiable-credentials.md b/docs/guides/authentication/verifiable-credentials.md index 8fc7a59..8951a2d 100644 --- a/docs/guides/authentication/verifiable-credentials.md +++ b/docs/guides/authentication/verifiable-credentials.md @@ -72,7 +72,7 @@ Returns the consent text shown to the user in the II dialog. This message must c #### 2. `derivation_origin` -Returns the URL used to derive the user's principal for this issuer. If you do not use [alternative derivation origins](../../reference/internet-identity-spec.md), return the canister's default URL: +Returns the URL used to derive the user's principal for this issuer. If you do not use [alternative derivation origins](../../references/internet-identity-spec.md), return the canister's default URL: ``` https://.icp0.io @@ -97,7 +97,7 @@ Issues the signed credential. This endpoint: - Verifies that `prepared_context` is consistent with the earlier preparation step. - Returns the signed credential as a JWT. -The credential is signed using a [canister signature](../../reference/ic-interface-spec.md): a signature produced by the canister's key, not an ECDSA or Ed25519 key. This means the canister must update `certified_data` in `prepare_credential` before the signature becomes available in `get_credential`. +The credential is signed using a [canister signature](../../references/ic-interface-spec/index.md#canister-signatures): a signature produced by the canister's key, not an ECDSA or Ed25519 key. This means the canister must update `certified_data` in `prepare_credential` before the signature becomes available in `get_credential`. ### Credential format convention @@ -329,7 +329,7 @@ The `verifiableCredential` array always contains exactly two JWTs in this order: #### Cryptographic verification -Both credentials are signed using [canister signatures](../../reference/ic-interface-spec.md). To verify them: +Both credentials are signed using [canister signatures](../../references/ic-interface-spec/index.md#canister-signatures). To verify them: 1. Decode the outer VP JWT. 2. Extract the two inner JWTs from `vp.verifiableCredential`. @@ -398,6 +398,6 @@ The VC protocol provides the following privacy guarantees: - Read the [VC specification](https://github.com/dfinity/internet-identity/blob/main/docs/vc-spec.md) for the full protocol details. - Explore the [verifiable credentials playground](https://github.com/dfinity/vc-playground) for issuer and relying party reference implementations. - Review [Internet Identity integration](internet-identity.md) for authentication setup. -- See the [Internet Identity specification](../../reference/internet-identity-spec.md) for alternative derivation origins and canister signature details. +- See the [Internet Identity specification](../../references/internet-identity-spec.md) for alternative derivation origins and canister signature details. diff --git a/docs/guides/backends/certified-variables.md b/docs/guides/backends/certified-variables.md index e0b282b..2fafb61 100644 --- a/docs/guides/backends/certified-variables.md +++ b/docs/guides/backends/certified-variables.md @@ -361,6 +361,7 @@ See [Frontend certification](../../guides/frontends/certification.md) for the as - [Security concepts](../../concepts/security.md): why query integrity matters and when to use certified variables vs replicated queries - [Frontend certification](../../guides/frontends/certification.md): HTTP asset certification for the asset canister -- [IC Interface Specification](../../reference/ic-interface-spec.md): the certified data system API and certificate format +- [IC Interface Specification: Certified Data](../../references/ic-interface-spec/canister-interface.md#system-api-certified-data): the certified data system API +- [IC Interface Specification: Certification](../../references/ic-interface-spec/certification.md): certificate format and delegation diff --git a/docs/guides/backends/https-outcalls.mdx b/docs/guides/backends/https-outcalls.mdx index 3719c12..e0f9c48 100644 --- a/docs/guides/backends/https-outcalls.mdx +++ b/docs/guides/backends/https-outcalls.mdx @@ -10,7 +10,7 @@ import CodeExample from '../../../src/components/CodeExample.astro'; [Canisters](../../concepts/canisters.md) can make HTTP requests to external web services using HTTPS outcalls. This lets your canister fetch offchain data, call REST APIs, or send notifications: all from onchain code. -HTTPS outcalls are available through the [IC management canister](../../reference/management-canister.md) (`aaaaa-aa`) via the `http_request` method. The `GET`, `HEAD`, and `POST` methods are supported. `HEAD` works identically to `GET` but returns only headers: useful for checking resource availability without downloading the body. Only HTTPS (not plain HTTP) is supported. +HTTPS outcalls are available through the [IC management canister](../../references/management-canister.md) (`aaaaa-aa`) via the `http_request` method. The `GET`, `HEAD`, and `POST` methods are supported. `HEAD` works identically to `GET` but returns only headers: useful for checking resource availability without downloading the body. Only HTTPS (not plain HTTP) is supported. For how the consensus mechanism works for outcalls, see [Concepts: HTTPS Outcalls](../../concepts/https-outcalls.md). @@ -146,7 +146,7 @@ For reference, on a 13-node subnet: - Per request byte: 5,200 cycles - Per `max_response_bytes` byte: 10,400 cycles -See [Cycles Costs](../../reference/cycles-costs.md) for the full pricing table. +See [Cycles Costs](../../references/cycles-costs.md) for the full pricing table. ## Limitations and pitfalls @@ -163,9 +163,9 @@ Use the "Full example in ICP Ninja" links above to deploy and test directly in t ## Next steps - [Concepts: HTTPS Outcalls](../../concepts/https-outcalls.md): how consensus works for outcalls -- [Management canister reference](../../reference/management-canister.md#http_request): full `http_request` parameter reference including all fields +- [Management canister reference](../../references/management-canister.md#http_request): full `http_request` parameter reference including all fields - [Exchange Rate Canister (XRC)](https://github.com/dfinity/exchange-rate-canister): a production service powered by HTTPS outcalls that fetches digital asset and fiat exchange rates - [Chain Fusion: Ethereum](../chain-fusion/ethereum.md): the EVM RPC canister uses HTTPS outcalls under the hood -- [Cycles Costs](../../reference/cycles-costs.md): outcall pricing details +- [Cycles Costs](../../references/cycles-costs.md): outcall pricing details {/* Upstream: informed by dfinity/portal docs/building-apps/network-features/using-http/https-outcalls/; dfinity/examples send_http_get, send_http_post */} diff --git a/docs/guides/backends/randomness.md b/docs/guides/backends/randomness.md index d629bf2..6d3b101 100644 --- a/docs/guides/backends/randomness.md +++ b/docs/guides/backends/randomness.md @@ -25,7 +25,7 @@ The management canister (`aaaaa-aa`) exposes `raw_rand`, which returns 32 bytes Because `raw_rand` is an update call to the management canister, it can only be invoked from an update context in your canister. **Randomness is not available in query calls**: a query executes on a single replica and cannot access the subnet-level random beacon. Attempting to call `raw_rand` from a query will trap. -See the [Management Canister reference](../../reference/management-canister.md#raw_rand) for the full API specification. +See the [Management Canister reference](../../references/management-canister.md#raw_rand) for the full API specification. ## Getting random bytes @@ -224,7 +224,7 @@ Note: this example predates `mo:core` and uses the older `Random.Finite` API. Th ## Next steps - [Verifiable Randomness (concept)](../../concepts/verifiable-randomness.md): how the IC's threshold VRF works -- [Management Canister](../../reference/management-canister.md): `raw_rand` API reference +- [Management Canister](../../references/management-canister.md): `raw_rand` API reference - [Data Integrity](../security/data-integrity.md): using randomness in a secure application design - [Inter-canister calls](../canister-calls/inter-canister-calls.md): async patterns and reentrancy diff --git a/docs/guides/backends/timers.mdx b/docs/guides/backends/timers.mdx index 907d654..0c9b8a2 100644 --- a/docs/guides/backends/timers.mdx +++ b/docs/guides/backends/timers.mdx @@ -215,7 +215,7 @@ Timer tasks are added to the canister's input queue. If the canister or subnet i The canister output queue is limited to 500 messages. This caps how many timers can fire in a single round. The CDK also enforces internal rate limits (250 concurrent timer calls globally, 5 per interval timer). -See [Cycles and costs](../../reference/cycles-costs.md) for current pricing. +See [Cycles and costs](../../references/cycles-costs.md) for current pricing. ## Heartbeats (legacy) @@ -243,7 +243,7 @@ The CDK timers library (`ic-cdk-timers` for Rust, `mo:core/Timer` for Motoko) bu 3. In `canister_global_timer`, runs each expired task as a self-canister call to isolate tasks from each other and from the library code 4. Reschedules recurring tasks at the end of their execution -For protocol internals, see [Timers](../../concepts/timers.md) and the [IC interface specification](../../reference/ic-interface-spec.md). +For protocol internals, see [Timers](../../concepts/timers.md) and the [IC interface specification](../../references/ic-interface-spec/index.md). ## Frequently asked questions @@ -279,6 +279,6 @@ For a complete working example with cycle tracking and multiple timers: - [Canister lifecycle](../canister-management/lifecycle.md): init, pre/post-upgrade hooks - [Timers (concept)](../../concepts/timers.md): how the IC protocol timer works -- [Cycles and costs](../../reference/cycles-costs.md): current pricing +- [Cycles and costs](../../references/cycles-costs.md): current pricing {/* Upstream: informed by dfinity/portal docs/building-apps/network-features/periodic-tasks-timers.mdx, docs/building-apps/network-features/time-and-timestamps.mdx, dfinity/cdk-rs ic-cdk-timers/src/lib.rs, and caffeinelabs/motoko-core src/Timer.mo */} diff --git a/docs/guides/canister-calls/calling-from-clients.md b/docs/guides/canister-calls/calling-from-clients.md index d7be79d..b08cf05 100644 --- a/docs/guides/canister-calls/calling-from-clients.md +++ b/docs/guides/canister-calls/calling-from-clients.md @@ -61,7 +61,7 @@ Crate documentation: [docs.rs/ic-agent](https://docs.rs/ic-agent/latest/ic_agent ### Community agents -Community-maintained agents are available for Go, Java/Android, Dart/Flutter, .NET, Elixir, and C. See [Developer Tools](../../reference/developer-tools.md#other-languages) for the full list. +Community-maintained agents are available for Go, Java/Android, Dart/Flutter, .NET, Elixir, and C. See [Developer Tools](../../references/developer-tools.md#other-languages) for the full list. ## JavaScript / TypeScript: using the agent diff --git a/docs/guides/canister-calls/candid.mdx b/docs/guides/canister-calls/candid.mdx index fc904ef..94192c6 100644 --- a/docs/guides/canister-calls/candid.mdx +++ b/docs/guides/canister-calls/candid.mdx @@ -94,7 +94,7 @@ Candid has a fixed set of types that map to native types in each supported langu > **JavaScript `opt T` tip:** The `[value] | []` representation is the raw IDL encoding. In practice, use `fromNullable()` and `toNullable()` from `@dfinity/utils` to convert between `opt` values and idiomatic JavaScript (`value | undefined`). -For the complete type reference, including subtyping rules, see the [Candid specification](../../reference/candid-spec.md). +For the complete type reference, including subtyping rules, see the [Candid specification](../../references/candid-spec.md). ## Generating `.did` files @@ -445,6 +445,6 @@ For type selector configuration and advanced options, see the [`ic-cdk-bindgen` - [Inter-canister calls](inter-canister-calls.md): make inter-canister calls using Candid interfaces - [Calling from clients](calling-from-clients.md): call canisters from JavaScript frontends and agents -- [Candid specification](../../reference/candid-spec.md): full type reference and subtyping rules +- [Candid specification](../../references/candid-spec.md): full type reference and subtyping rules {/* Upstream: informed by dfinity/portal docs/building-apps/interact-with-canisters/candid/ (3 files), docs/building-apps/developer-tools/cdks/rust/generating-candid.mdx, icp-cli concepts/binding-generation.md, ic-cdk-bindgen README, and @icp-sdk/bindgen. Type mappings verified against .sources/candid (spec), .sources/motoko (IDL-Motoko.md), and .sources/cdk-rs. */} diff --git a/docs/guides/canister-management/cycles-management.mdx b/docs/guides/canister-management/cycles-management.mdx index 6b5a35c..3b85509 100644 --- a/docs/guides/canister-management/cycles-management.mdx +++ b/docs/guides/canister-management/cycles-management.mdx @@ -57,7 +57,7 @@ icp cycles balance -n ic # Output: ~5T cycles ``` -**Budget guidance:** Plan for 1–2T cycles per canister as a starting balance. A simple backend canister with moderate traffic costs roughly 0.1–0.5T cycles per month, though this varies with storage and call volume. See the [cycles costs reference](../../reference/cycles-costs.md) for per-operation pricing. +**Budget guidance:** Plan for 1–2T cycles per canister as a starting balance. A simple backend canister with moderate traffic costs roughly 0.1–0.5T cycles per month, though this varies with storage and call volume. See the [cycles costs reference](../../references/cycles-costs.md) for per-operation pricing. ## Checking canister cycle balances @@ -411,7 +411,7 @@ icp canister top-up backend --amount 1T -n ic - [Canister settings](settings.md): Freezing threshold, memory allocation, compute allocation - [Canister lifecycle](lifecycle.md): Create, install, upgrade, and delete canisters -- [Cycles costs reference](../../reference/cycles-costs.md): Exact cost tables per operation +- [Cycles costs reference](../../references/cycles-costs.md): Exact cost tables per operation - [Cycles](../../concepts/cycles.md): Why canisters pay for execution - [Reproducible builds](reproducible-builds.md): Verify your WASM is trustworthy before deploying - [icp-cli docs](https://cli.internetcomputer.org/): Full command reference diff --git a/docs/guides/canister-management/large-wasm.md b/docs/guides/canister-management/large-wasm.md index 76a416c..9380ff0 100644 --- a/docs/guides/canister-management/large-wasm.md +++ b/docs/guides/canister-management/large-wasm.md @@ -72,7 +72,7 @@ When compression alone is not enough, the Wasm chunk store lets you upload modul 1. **Upload chunks**: Call `upload_chunk` on the management canister to store up to 1 MiB chunks in the target canister's chunk store. Each call returns the SHA-256 hash of the stored chunk. 2. **Assemble and install**: Call `install_chunked_code` with the ordered list of chunk hashes. The system concatenates the chunks, verifies the aggregate hash matches `wasm_module_hash`, and installs the result as if you had called `install_code` directly. -The chunk store is bounded: each chunk is at most 1 MiB, and there is a maximum number of chunks per store (`CHUNK_STORE_SIZE`, defined in the IC interface spec: see the [management canister reference](../../reference/management-canister.md) for the exact value). You can inspect stored chunks with `stored_chunks` and clear the store with `clear_chunk_store`. +The chunk store is bounded: each chunk is at most 1 MiB, and there is a maximum number of chunks per store (`CHUNK_STORE_SIZE`, defined in the IC interface spec: see the [management canister reference](../../references/management-canister.md) for the exact value). You can inspect stored chunks with `stored_chunks` and clear the store with `clear_chunk_store`. ### icp-cli handles this automatically @@ -248,7 +248,7 @@ rustup component add rust-src --toolchain nightly ## Next steps - [Canister optimization](optimization.md): reduce Wasm size before reaching for the chunk store -- [Execution errors reference](../../reference/execution-errors.md): Wasm size and chunk store error codes +- [Execution errors reference](../../references/execution-errors.md): Wasm size and chunk store error codes - [Canister lifecycle](lifecycle.md): deployment modes and install options diff --git a/docs/guides/canister-management/lifecycle.mdx b/docs/guides/canister-management/lifecycle.mdx index a32d4f8..aab923e 100644 --- a/docs/guides/canister-management/lifecycle.mdx +++ b/docs/guides/canister-management/lifecycle.mdx @@ -304,7 +304,7 @@ Both approaches use [canister snapshots](snapshots.md) to transfer state. For th ## Programmatic canister management -Canisters can manage other canisters by calling the [management canister](../../reference/management-canister.md) (`aaaaa-aa`). This enables patterns like canister factories that create and manage child canisters dynamically. +Canisters can manage other canisters by calling the [management canister](../../references/management-canister.md) (`aaaaa-aa`). This enables patterns like canister factories that create and manage child canisters dynamically. diff --git a/docs/guides/canister-management/logs.md b/docs/guides/canister-management/logs.md index 3f98b82..242dd5c 100644 --- a/docs/guides/canister-management/logs.md +++ b/docs/guides/canister-management/logs.md @@ -251,7 +251,7 @@ If the `"name"` section is absent, backtraces will not be available. ## Query statistics -Each canister exposes cumulative statistics about its query call traffic. These are available through the [management canister](../../reference/management-canister.md)'s `canister_status` method. +Each canister exposes cumulative statistics about its query call traffic. These are available through the [management canister](../../references/management-canister.md)'s `canister_status` method. The statistics are cumulative since the canister was created. They are updated approximately once per epoch rather than in real time. diff --git a/docs/guides/canister-management/optimization.md b/docs/guides/canister-management/optimization.md index d5f7e33..f6d9777 100644 --- a/docs/guides/canister-management/optimization.md +++ b/docs/guides/canister-management/optimization.md @@ -231,7 +231,7 @@ Most production canisters benefit from combining several techniques: ## Next steps - [Large Wasm](large-wasm.md): when binary size exceeds the upload limit -- [Cycles costs](../../reference/cycles-costs.md): how Wasm size and instruction count map to cycle charges +- [Cycles costs](../../references/cycles-costs.md): how Wasm size and instruction count map to cycle charges - [Canister lifecycle](lifecycle.md): how optimized builds integrate with the icp-cli deploy workflow diff --git a/docs/guides/canister-management/settings.mdx b/docs/guides/canister-management/settings.mdx index a002be6..aafca42 100644 --- a/docs/guides/canister-management/settings.mdx +++ b/docs/guides/canister-management/settings.mdx @@ -69,7 +69,7 @@ settings: A value of `50` means the canister gets 50% of an execution core and is scheduled at least every other round. A value of `100` means the canister runs every round. -Compute allocation incurs a rental fee based on time and allocation percentage, regardless of whether the canister actually executes. This increases idle [cycle](../../concepts/cycles.md) consumption. See [cycles costs](../../reference/cycles-costs.md) for pricing details. +Compute allocation incurs a rental fee based on time and allocation percentage, regardless of whether the canister actually executes. This increases idle [cycle](../../concepts/cycles.md) consumption. See [cycles costs](../../references/cycles-costs.md) for pricing details. ### Memory allocation @@ -90,7 +90,7 @@ Supported suffixes: `kb` (1,000), `kib` (1,024), `mb` (1,000,000), `mib` (1,048, When set, the canister draws new Wasm and stable memory from the pre-allocated pool. If usage exceeds the allocation, additional memory is allocated on demand and may fail if the subnet is at capacity. -Like compute allocation, memory allocation incurs a rental fee based on time and allocated amount, regardless of actual usage. See [cycles costs](../../reference/cycles-costs.md) for pricing. +Like compute allocation, memory allocation incurs a rental fee based on time and allocated amount, regardless of actual usage. See [cycles costs](../../references/cycles-costs.md) for pricing. ### Freezing threshold @@ -436,7 +436,7 @@ async fn set_freezing_threshold(canister_id: Principal, seconds: u64) { Only a controller of the target canister can call `update_settings`. Set fields to `null`/`None` to leave them unchanged. -For the full management canister interface, see the [management canister reference](../../reference/management-canister.md). +For the full management canister interface, see the [management canister reference](../../references/management-canister.md). ## Common control models @@ -454,7 +454,7 @@ How you configure controllers depends on the trust model for your canister: - [Canister lifecycle](lifecycle.md): Create, deploy, upgrade, stop, and delete canisters. - [Cycles management](cycles-management.md): Monitor and top up cycle balances. -- [Cycles costs reference](../../reference/cycles-costs.md): Pricing for compute and memory allocation. -- [Management canister reference](../../reference/management-canister.md): Full interface specification. +- [Cycles costs reference](../../references/cycles-costs.md): Pricing for compute and memory allocation. +- [Management canister reference](../../references/management-canister.md): Full interface specification. {/* Upstream: informed by dfinity/portal (docs/building-apps/canister-management/settings.mdx, docs/building-apps/canister-management/control.mdx, docs/references/_attachments/ic.did (snapshot_visibility field); dfinity/icp-cli) docs/reference/canister-settings.md, docs/reference/cli.md; dfinity/icskills: skills/cycles-management/SKILL.md */} diff --git a/docs/guides/canister-management/subnet-selection.md b/docs/guides/canister-management/subnet-selection.md index 926caec..0062909 100644 --- a/docs/guides/canister-management/subnet-selection.md +++ b/docs/guides/canister-management/subnet-selection.md @@ -100,7 +100,7 @@ Subnets enforce a storage reservation policy above 750 GiB of total utilization. If you expect your canister to use significant storage, check the current utilization of candidate subnets on the [ICP Dashboard](https://dashboard.internetcomputer.org/subnets) before deploying. Choosing a subnet with available headroom avoids unexpected reservation costs as your canister grows. -For details on storage costs and the reservation formula, see [Cycles costs](../../reference/cycles-costs.md). +For details on storage costs and the reservation formula, see [Cycles costs](../../references/cycles-costs.md). ## Troubleshooting @@ -119,8 +119,8 @@ Note that any canister ID change means losing access to any threshold signature ## Next steps -- [Cycles costs](../../reference/cycles-costs.md): Cost tables and the subnet multiplier formula -- [Subnet types reference](../../reference/subnet-types.md): Full reference for all subnet types with node counts and properties +- [Cycles costs](../../references/cycles-costs.md): Cost tables and the subnet multiplier formula +- [Subnet types reference](../../references/subnet-types.md): Full reference for all subnet types with node counts and properties - [Canister snapshots](snapshots.md): Transfer state between canisters when migrating subnets - [Network overview](../../concepts/network-overview.md): How subnets fit into the ICP architecture diff --git a/docs/guides/chain-fusion/bitcoin.mdx b/docs/guides/chain-fusion/bitcoin.mdx index 0f69299..8eaeb48 100644 --- a/docs/guides/chain-fusion/bitcoin.mdx +++ b/docs/guides/chain-fusion/bitcoin.mdx @@ -1176,7 +1176,7 @@ docker stop bitcoind && docker rm bitcoind - [Chain-key cryptography](../../concepts/chain-key-cryptography.md): learn how threshold ECDSA and Schnorr signatures work - [Chain-key tokens](../digital-assets/chain-key-tokens.md): explore ckBTC, ckETH, and other chain-key tokens - [Ethereum integration](ethereum.md): apply similar patterns for Ethereum -- [Management canister reference](../../reference/management-canister.md): full API reference for `sign_with_ecdsa`, `sign_with_schnorr`, and other management canister methods (note: the `bitcoin_*` methods in the management canister are deprecated; use the Bitcoin canister directly) +- [Management canister reference](../../references/management-canister.md): full API reference for `sign_with_ecdsa`, `sign_with_schnorr`, and other management canister methods (note: the `bitcoin_*` methods in the management canister are deprecated; use the Bitcoin canister directly) - [Bitcoin canister API specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md): detailed API documentation - [Bitcoin integration (Learn Hub)](https://learn.internetcomputer.org/hc/en-us/articles/34211154520084): protocol-level details of how ICP connects to Bitcoin diff --git a/docs/guides/chain-fusion/chain-fusion-signer.md b/docs/guides/chain-fusion/chain-fusion-signer.md index 15a022d..b6c6271 100644 --- a/docs/guides/chain-fusion/chain-fusion-signer.md +++ b/docs/guides/chain-fusion/chain-fusion-signer.md @@ -13,7 +13,7 @@ The signer charges callers in cycles for each API call. You pre-approve the sign ## Prerequisites -- An ICP identity with cycles in the [Cycles Ledger](../../reference/system-canisters.md#cycles-ledger) (`um5iw-rqaaa-aaaaq-qaaba-cai`) +- An ICP identity with cycles in the [Cycles Ledger](../../references/system-canisters.md#cycles-ledger) (`um5iw-rqaaa-aaaaq-qaaba-cai`) - icp-cli installed and authenticated (`icp identity whoami`) - For offline address derivation: Node.js and `npx` @@ -269,7 +269,7 @@ These variants are defined by [papi](https://github.com/dfinity/papi), an open-s - [Bitcoin integration guide](bitcoin.md): build a full Bitcoin app with your own signing backend - [Ethereum integration guide](ethereum.md): EVM RPC canister for reading Ethereum state -- [Cycles Ledger](../../reference/system-canisters.md#cycles-ledger): fund your account with cycles +- [Cycles Ledger](../../references/system-canisters.md#cycles-ledger): fund your account with cycles - [Offline key derivation](offline-key-derivation.md): derive ETH/BTC addresses for any canister principal without a management canister call - [papi](https://github.com/dfinity/papi): add the same `CallerPaysIcrc2Cycles` / `PatronPaysIcrc2Cycles` payment pattern to your own canister diff --git a/docs/guides/chain-fusion/offline-key-derivation.md b/docs/guides/chain-fusion/offline-key-derivation.md index 2fd1392..a722fe9 100644 --- a/docs/guides/chain-fusion/offline-key-derivation.md +++ b/docs/guides/chain-fusion/offline-key-derivation.md @@ -85,7 +85,7 @@ For deriving Chain Fusion Signer addresses specifically (ETH/BTC for a given pri ## Next steps - [Chain Fusion Signer](chain-fusion-signer.md): sign transactions for Bitcoin and Ethereum from web apps and CLI -- [Management canister reference](../../reference/management-canister.md#chain-key-signing): the `ecdsa_public_key` and `schnorr_public_key` management canister methods +- [Management canister reference](../../references/management-canister.md#chain-key-signing): the `ecdsa_public_key` and `schnorr_public_key` management canister methods - [Chain-key cryptography](../../concepts/chain-key-cryptography.md): how threshold key derivation works diff --git a/docs/guides/digital-assets/chain-key-tokens.mdx b/docs/guides/digital-assets/chain-key-tokens.mdx index 24279aa..673bc1a 100644 --- a/docs/guides/digital-assets/chain-key-tokens.mdx +++ b/docs/guides/digital-assets/chain-key-tokens.mdx @@ -9,7 +9,7 @@ import { Tabs, TabItem } from '@astrojs/starlight/components'; Chain-key tokens are ICP-native representations of assets from other blockchains. Each one is backed 1:1 by the original asset and controlled entirely by ICP canisters. No bridges, no wrapped tokens, no third-party custodians. -All chain-key tokens implement the [ICRC-1 standard](../../reference/digital-asset-standards.md#icrc-1-fungible-tokens). The deposit and withdrawal flows use [ICRC-2](../../reference/digital-asset-standards.md#icrc-2-approve-and-transfer-from) `icrc2_approve` to authorize the minter to burn tokens on your behalf. +All chain-key tokens implement the [ICRC-1 standard](../../references/digital-asset-standards.md#icrc-1-fungible-tokens). The deposit and withdrawal flows use [ICRC-2](../../references/digital-asset-standards.md#icrc-2-approve-and-transfer-from) `icrc2_approve` to authorize the minter to burn tokens on your behalf. This guide covers deposit and withdrawal flows for each asset, plus the pattern for issuing per-user deposit addresses from a backend canister. @@ -397,7 +397,7 @@ The ERC-20 deposit flow uses the same helper contract and minter: - A 32-byte subaccount (`0x` for the default account). 3. The minter detects the `ReceivedEthOrErc20` event and mints the corresponding ckERC20 to `(principal, subaccount)`. -Supported ERC-20 tokens on mainnet: USDC, USDT, EURC, WBTC, wstETH, LINK, UNI, SHIB, PEPE, XAUT, and OCT. For canister IDs, see [Chain-Key Token Canister IDs](../../reference/chain-key-canister-ids.md#ckerc20). For the authoritative current list including any newly added tokens, call `get_minter_info` on the ckETH minter and check `supported_ckerc20_tokens`. +Supported ERC-20 tokens on mainnet: USDC, USDT, EURC, WBTC, wstETH, LINK, UNI, SHIB, PEPE, XAUT, and OCT. For canister IDs, see [Chain-Key Token Canister IDs](../../references/chain-key-canister-ids.md#ckerc20). For the authoritative current list including any newly added tokens, call `get_minter_info` on the ckETH minter and check `supported_ckerc20_tokens`. ### Withdrawal: ckETH or ckERC20 → ETH / ERC-20 @@ -520,7 +520,7 @@ icp canister call mxzaz-hqaaa-aaaar-qaada-cai icrc1_balance_of \ -n ic ``` -For canister IDs for all other chain-key tokens, see [Chain-Key Token Canister IDs](../../reference/chain-key-canister-ids.md). +For canister IDs for all other chain-key tokens, see [Chain-Key Token Canister IDs](../../references/chain-key-canister-ids.md). ## Next steps @@ -528,6 +528,6 @@ For canister IDs for all other chain-key tokens, see [Chain-Key Token Canister I - [Bitcoin integration](../chain-fusion/bitcoin.md): native BTC UTXO access and threshold signing - [Ethereum integration](../chain-fusion/ethereum.md): calling Ethereum contracts from ICP canisters - [Wallet integration](wallet-integration.md): add wallet signing to your app -- [Digital Asset Standards](../../reference/digital-asset-standards.md): formal ICRC standard specifications for fungible assets, NFTs, and their extensions +- [Digital Asset Standards](../../references/digital-asset-standards.md): formal ICRC standard specifications for fungible assets, NFTs, and their extensions {/* Upstream: informed by dfinity/icskills skills/ckbtc/SKILL.md; dfinity/icskills skills/icrc-ledger/SKILL.md; dfinity/portal docs/defi/chain-key-tokens/cketh/overview.mdx; dfinity/ic rs/ethereum/cketh/docs/cketh.adoc; dfinity/ic rs/ethereum/cketh/docs/ckerc20.adoc; dfinity/ic rs/bitcoin/ckbtc (ckBTC minter API); dfinity/ic rs/dogecoin/ckdoge (ckDOGE minter API and canister IDs); dfinity/cksol (ckSOL minter API and canister IDs) */} diff --git a/docs/guides/digital-assets/ledgers.mdx b/docs/guides/digital-assets/ledgers.mdx index f6c1439..085bcb9 100644 --- a/docs/guides/digital-assets/ledgers.mdx +++ b/docs/guides/digital-assets/ledgers.mdx @@ -7,7 +7,7 @@ sidebar: import { Tabs, TabItem } from '@astrojs/starlight/components'; -Digital assets on ICP are managed by **ledger canisters** that implement the [ICRC digital asset standards](../../reference/digital-asset-standards.md). The ICP ledger is fully ICRC-1 and ICRC-2 compliant: code that works with the ICP ledger works identically with ckBTC, ckETH, or any ICRC-1 compatible asset. You only need to swap the canister ID and fee. The ICRC specifications use the term "token" throughout their text. +Digital assets on ICP are managed by **ledger canisters** that implement the [ICRC digital asset standards](../../references/digital-asset-standards.md). The ICP ledger is fully ICRC-1 and ICRC-2 compliant: code that works with the ICP ledger works identically with ckBTC, ckETH, or any ICRC-1 compatible asset. You only need to swap the canister ID and fee. The ICRC specifications use the term "token" throughout their text. Each ledger is paired with an **index canister** that continuously syncs the ledger's blocks and provides efficient per-account transaction history queries. Together they form the **ledger suite**. @@ -21,7 +21,7 @@ This guide covers the most common operations: transfers, approvals, subaccounts, > Query `icrc1_fee` and `icrc1_decimals` at runtime rather than hardcoding values. -For chain-key token canister IDs (ckBTC, ckETH, ckDOGE, ckSOL, and ckERC20), see [Chain-Key Token Canister IDs](../../reference/chain-key-canister-ids.md). For chain-key token specifics (minting, deposits, withdrawals), see [Chain-key tokens](chain-key-tokens.md). +For chain-key token canister IDs (ckBTC, ckETH, ckDOGE, ckSOL, and ckERC20), see [Chain-Key Token Canister IDs](../../references/chain-key-canister-ids.md). For chain-key token specifics (minting, deposits, withdrawals), see [Chain-key tokens](chain-key-tokens.md). ## Transferring assets (ICRC-1) @@ -503,7 +503,7 @@ icp canister call qhbym-qaaaa-aaaaa-aaafq-cai get_account_transactions \ The response includes a `transactions` list and the account's current `balance` at the tip. Paginate backwards using the `start` field (the oldest block index from the previous response). -**ICRC-3** is the standard that defines how ledgers expose their block structure: the block schema, archive model, and `icrc3_get_blocks` method. It is why index canisters, Rosetta nodes, and custom indexers can all read the same block data in a consistent, verifiable format. All system ledgers on ICP implement ICRC-3. See [ICRC-3: Transaction log](../../reference/digital-asset-standards.md#icrc-3-transaction-log) for the block schema and method signatures. +**ICRC-3** is the standard that defines how ledgers expose their block structure: the block schema, archive model, and `icrc3_get_blocks` method. It is why index canisters, Rosetta nodes, and custom indexers can all read the same block data in a consistent, verifiable format. All system ledgers on ICP implement ICRC-3. See [ICRC-3: Transaction log](../../references/digital-asset-standards.md#icrc-3-transaction-log) for the block schema and method signatures. For client-side indexing across multiple ICRC-1 ledgers (exchange integrations, custody platforms, analytics), use the [Rosetta API](rosetta.md). The ICRC Rosetta implementation supports querying balances and transaction history across any number of ICRC-1 compatible ledgers simultaneously. @@ -577,8 +577,8 @@ icp canister call icrc1_ledger icrc1_transfer \ ## Next steps -- [Digital Asset Standards](../../reference/digital-asset-standards.md): ICRC-1, ICRC-2, ICRC-3, ICRC-7, and ICRC-37 specifications including NFT standards -- [Chain-Key Token Canister IDs](../../reference/chain-key-canister-ids.md): mainnet and testnet canister IDs for all chain-key tokens +- [Digital Asset Standards](../../references/digital-asset-standards.md): ICRC-1, ICRC-2, ICRC-3, ICRC-7, and ICRC-37 specifications including NFT standards +- [Chain-Key Token Canister IDs](../../references/chain-key-canister-ids.md): mainnet and testnet canister IDs for all chain-key tokens - [Chain-key tokens](chain-key-tokens.md): minting, depositing, and withdrawing ckBTC, ckETH, ckDOGE, and ckSOL - [Rosetta API](rosetta.md): client-side indexing and transaction construction for exchanges and custody platforms - [Wallet integration](wallet-integration.md): connecting wallets to your app diff --git a/docs/guides/digital-assets/rosetta.md b/docs/guides/digital-assets/rosetta.md index 6bbc46a..0dc753a 100644 --- a/docs/guides/digital-assets/rosetta.md +++ b/docs/guides/digital-assets/rosetta.md @@ -510,6 +510,6 @@ Each directory includes a `requirements.txt` and a `run_tests.sh` script for iso ## Next steps - [Ledgers](ledgers.md): transfer and manage assets directly from canister code -- [Digital Asset Standards](../../reference/digital-asset-standards.md): formal ICRC standard specifications for fungible assets, NFTs, and their extensions +- [Digital Asset Standards](../../references/digital-asset-standards.md): formal ICRC standard specifications for fungible assets, NFTs, and their extensions diff --git a/docs/guides/digital-assets/wallet-integration.md b/docs/guides/digital-assets/wallet-integration.md index 54d9dec..7614b22 100644 --- a/docs/guides/digital-assets/wallet-integration.md +++ b/docs/guides/digital-assets/wallet-integration.md @@ -24,7 +24,7 @@ Use Internet Identity for login. Use a wallet signer when your app needs users t ## ICRC signer standards -The signer model is defined by five ICRC standards (ICRC-21, 25, 27, 29, and 49) covering consent messages, permissions, account discovery, and canister call routing. For details on each, see [Wallet signer standards](../../reference/icrc-standards.md#wallet-signer-standards). A compliant wallet such as [OISY](https://oisy.com) implements all five. +The signer model is defined by five ICRC standards (ICRC-21, 25, 27, 29, and 49) covering consent messages, permissions, account discovery, and canister call routing. For details on each, see [Wallet signer standards](../../references/icrc-standards.md#wallet-signer-standards). A compliant wallet such as [OISY](https://oisy.com) implements all five. ## How it works @@ -242,6 +242,6 @@ Both libraries are expected to move to the `@icp-sdk` namespace on npm and will - [Internet Identity integration](../authentication/internet-identity.md): add authentication alongside wallet signing - [Ledgers](ledgers.md): transfer and manage assets with ledgers that implement digital asset standards -- [Digital Asset Standards](../../reference/digital-asset-standards.md): formal ICRC specifications for fungible assets, NFTs, and their extensions +- [Digital Asset Standards](../../references/digital-asset-standards.md): formal ICRC specifications for fungible assets, NFTs, and their extensions diff --git a/docs/guides/frontends/certification.md b/docs/guides/frontends/certification.md index 8e79f8d..65ddeaf 100644 --- a/docs/guides/frontends/certification.md +++ b/docs/guides/frontends/certification.md @@ -318,6 +318,6 @@ For the full working example including a backend canister, see the [certified-co - [Asset canister](asset-canister.md): deploy and configure the standard asset canister with automatic certification - [Certified variables](../backends/certified-variables.md): certify Candid query responses from backend canisters - [Security concepts](../../concepts/security.md): why query integrity matters -- [HTTP Gateway specification](../../reference/http-gateway-spec.md): how boundary nodes verify responses +- [HTTP Gateway specification](../../references/http-gateway-spec.md): how boundary nodes verify responses diff --git a/docs/guides/security/canister-upgrades.md b/docs/guides/security/canister-upgrades.md index c00502d..161a0b0 100644 --- a/docs/guides/security/canister-upgrades.md +++ b/docs/guides/security/canister-upgrades.md @@ -124,7 +124,7 @@ fn pre_upgrade() { } ``` -When `pre_upgrade` traps due to instruction exhaustion, the canister cannot be upgraded. The `skip_pre_upgrade` flag (an emergency escape hatch via the management canister's `install_code` API (see [Management canister reference](../../reference/management-canister.md)) bypasses the hook) but anything the hook would have saved is lost. Use stable structures so the upgrade path cannot brick itself under load. +When `pre_upgrade` traps due to instruction exhaustion, the canister cannot be upgraded. The `skip_pre_upgrade` flag (an emergency escape hatch via the management canister's `install_code` API (see [Management canister reference](../../references/management-canister.md)) bypasses the hook) but anything the hook would have saved is lost. Use stable structures so the upgrade path cannot brick itself under load. ## Candid interface compatibility diff --git a/docs/guides/security/dos-prevention.md b/docs/guides/security/dos-prevention.md index 0368492..d782f04 100644 --- a/docs/guides/security/dos-prevention.md +++ b/docs/guides/security/dos-prevention.md @@ -28,7 +28,7 @@ Every ingress message (external call to your canister) costs cycles. The cost in - Per-instruction fees for all code executed before a trap or rejection - Candid decoding, which runs before your method body -This means an attacker can drain your cycles simply by sending many messages. The canister pays for Candid decoding and early checks even when it rejects the call. See [Cycles costs](../../reference/cycles-costs.md) for exact figures. +This means an attacker can drain your cycles simply by sending many messages. The canister pays for Candid decoding and early checks even when it rejects the call. See [Cycles costs](../../references/cycles-costs.md) for exact figures. ### Use inspect_message as a first-pass filter @@ -223,7 +223,7 @@ The IC enforces hard limits on message execution. If your canister frequently ap | Wasm heap memory | 4 GiB (wasm32) | | Wasm stable memory | 500 GiB | -Source: [Cycles costs reference](../../reference/cycles-costs.md). +Source: [Cycles costs reference](../../references/cycles-costs.md). ### Prevent memory exhaustion @@ -288,7 +288,7 @@ canisters: compute_allocation: 10 # Guaranteed 10% of one execution core ``` -A value of `10` means the canister is scheduled at least every 10 consensus rounds. Compute allocation incurs an ongoing rental fee (10M cycles per percentage point per second on a 13-node subnet). Only set it if you need guaranteed throughput under load. See [Cycles costs](../../reference/cycles-costs.md). +A value of `10` means the canister is scheduled at least every 10 consensus rounds. Compute allocation incurs an ongoing rental fee (10M cycles per percentage point per second on a 13-node subnet). Only set it if you need guaranteed throughput under load. See [Cycles costs](../../references/cycles-costs.md). ### Memory allocation @@ -338,7 +338,7 @@ Chain-key signing (threshold ECDSA/Schnorr), HTTPS outcalls, and Bitcoin API cal - [Access management](access-management.md): caller checks, anonymous principal rejection, and role-based guards - [Inter-canister call safety](inter-canister-calls.md): TOCTOU vulnerabilities and the CallerGuard pattern - [Canister settings](../canister-management/settings.md): freezing threshold, memory allocation, and compute allocation -- [Cycles costs](../../reference/cycles-costs.md): exact cost tables and resource limits +- [Cycles costs](../../references/cycles-costs.md): exact cost tables and resource limits - [Security model](../../concepts/security.md): IC trust boundaries and threat model overview diff --git a/docs/index.mdx b/docs/index.mdx index 96b3265..d3f1b16 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -33,7 +33,7 @@ Teach your AI coding agent the patterns, APIs, and deployment workflows it needs - + ## External resources diff --git a/docs/reference/ic-interface-spec.md b/docs/reference/ic-interface-spec.md deleted file mode 100644 index c19d610..0000000 --- a/docs/reference/ic-interface-spec.md +++ /dev/null @@ -1,9954 +0,0 @@ ---- -title: "IC Interface Specification" -description: "The authoritative formal specification of the Internet Computer's external interfaces: System API, HTTPS interface, certified data, management canister, and abstract behavior" -sidebar: - order: 11 ---- - - -## Introduction - -Welcome to *the Internet Computer*! We speak of "the" Internet Computer, because although under the hood a large number of physical computers are working together in a blockchain protocol, in the end we have the appearance of a single, shared, secure and world-wide accessible computer. Developers who want to build decentralized applications (or *dapps* for short) that run on the Internet Computer blockchain and end-users who want to use those dapps need to know very little, if anything, about the underlying protocol. However, knowing some details about the interfaces that the Internet Computer exposes can allow interested developers and architects to take fuller advantages of the unique features that the Internet Computer provides. - -### Target audience - -This document describes this *external* view of the Internet Computer, i.e. the low-level interfaces it provides to dapp developers and users, and what will happen when they use these interfaces. - -:::note - -While this document describes the external interface and behavior of the Internet Computer, it is not intended as end-user or end-developer documentation. Most developers will interact with the Internet Computer through additional tooling like the SDK, Canister Development Kits and Motoko. Please see the [developer docs](/) for suitable documentation. - -::: - -The target audience of this document are - -- those who use these low-level interfaces (e.g. implement agents, canister developments kits, emulators, other tooling). - -- those who implement these low-level interfaces (e.g. developers of the Internet Computer implementation) - -- those who want to understand the intricacies of the Internet Computer's behavior in great detail (e.g. to do a security analysis) - -:::note - -This document is a rigorous, technically dense reference. It is not an introduction to the Internet Computer, and as such most useful to those who understand the high-level concepts. Please see more high-level documentation first. - -::: - -### Scope of this document - -If you think of the Internet Computer as a distributed engine that executes WebAssembly-based dapps, then this document describes exclusively the aspect of executing those dapps. To the extent possible, this document will *not* talk about consensus protocols, nodes, subnets, orthogonal persistence or governance. - -This document tries to be implementation agnostic: It would apply just as well to a (hypothetical) compatible reimplementation of the Internet Computer. This implies that this document does not cover interfaces towards those running the Internet Computer (e.g. data center operators, protocol developers, governance users), as topics like node update, monitoring, logging are inherently tied to the actual *implementation* and its architecture. - -### Overview of the Internet Computer - -Dapps on the Internet Computer, or *IC* for short, are implemented as *canister smart contracts*, or *canisters* for short. If you want to build on the Internet Computer as a dapp developer, you first create a *canister module* that contains the WebAssembly code and configuration for your dapp, and deploy it using the [HTTPS interface](#http-interface). You can create canister modules using the Motoko language and the SDK, which is more convenient. If you want to use your own tooling, however, then this document describes [what a canister module looks like](#canister-module-format) and how the [WebAssembly code can interact with the IC](#system-api). - -Once your dapp is running on the Internet Computer, it is a canister smart contract, and users can interact with it. They can use the [HTTPS interface](#http-interface) to interact with the canister according to the [System API](#system-api). - -The user can also use the HTTPS interface to issue read-only queries, which are faster, but cannot change the state of a canister. - -```plantuml - actor Developer - actor User - participant "Internet Computer" as IC - participant "Canister 1" as Can1 - Developer -> IC : /submit create canister - create Can1 - IC -> Can1 : create - Developer <-- IC : canister-id=1 - Developer -> IC : /submit install module - IC -> Can1 : initialize - ||| - User -> IC : /submit call "hello" - IC -> Can1 : hello - return "Hello world!" - User <-- IC : "Hello World!" -``` -**A typical use of the Internet Computer. (This is a simplified view; some of the arrows represent multiple interaction steps or polling.)** - -Sections "[HTTPS Interface](#http-interface)" and "[Canister interface (System API)](#system-api)" describe these interfaces, together with a brief description of what they do. Afterwards, you will find a [more formal description](#abstract-behavior) of the Internet Computer that describes its abstract behavior with more rigor. - -### Nomenclature - -To get some consistency in this document, we try to use the following terms with precision: - -We avoid the term "client", as it could be the client of the Internet Computer or the client inside the distributed network that makes up the Internet Computer. Instead, we use the term *user* to denote the external entity interacting with the Internet Computer, even if in most cases it will be some code (sometimes called "agent") acting on behalf of a (human) user. - -The public entry points of canisters are called *methods*. Methods can be declared to be either *update methods* (state mutation is preserved, can call update and query methods of arbitrary canisters), *query methods* (state mutation is discarded, no further calls can be made), or *composite query* methods (state mutation is discarded, can call query and composite query methods of canisters on the same subnet). - -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. -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. - -WebAssembly *functions* are exported by the WebAssembly module or provided by the System API. These are *invoked* and can either *trap* or *return*, possibly with a return value. A trap is caused by an irrecoverable error in the WebAssembly module (e.g., division by zero) or System API execution (e.g., running out of memory or exceeding the instruction limit for a single message execution imposed by the Internet Computer). Functions, too, have parameters and take arguments. - -External *users* interact with the Internet Computer by issuing *requests* on the HTTPS interface. Requests have responses which can either be replies or rejects. Some requests cause internal messages to be created. - -Canisters and users are identified by a *principal*, sometimes also called an *id*. - -## Pervasive concepts - -Before going into the details of the four public interfaces described in this document (namely the agent-facing [HTTPS interface](#http-interface), the canister-facing [System API](#system-api), the [virtual Management canister](#ic-management-canister) and the [System State Tree](#state-tree)), this section introduces some concepts that transcend multiple interfaces. - -### Unspecified constants and limits - -This specification may refer to certain constants and limits without specifying their concrete value (yet), i.e. they are implementation defined. Many are resource limits which are relevant only to specify the error-handling behavior of the IC (which, as mentioned above, is also not yet precisely described in this document). This list is not complete. - -- `MAX_CYCLES_PER_MESSAGE` - - Amount of cycles that a canister has to have before a message is attempted to be executed, which is deducted from the canister balance before message execution. See [Message execution](#rule-message-execution). - -- `MAX_CYCLES_PER_RESPONSE` - - Amount of cycles that the IC sets aside when a canister performs a call. This is used to pay for processing the response message, and unused cycles after the execution of the response are refunded. See [Message execution](#rule-message-execution). - -- `MAX_CYCLES_PER_QUERY` - - Maximum amount of cycles that can be used in total (across all calls to query and composite query methods and their callbacks) during evaluation of a query call. - -- `CHUNK_STORE_SIZE` - - 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. - -- `DEFAULT_PROVISIONAL_CYCLES_BALANCE` - - Amount of cycles allocated to a new canister by default, if not explicitly specified. See [IC method](#ic-provisional_create_canister_with_cycles). - -- `MAX_CALL_DEPTH_COMPOSITE_QUERY` - - Maximum nesting level of calls during evaluation of a query call to a composite query method. - -- `MAX_WALL_CLOCK_TIME_COMPOSITE_QUERY` - - Maximum wall clock time spent on evaluation of a query call. - -- `MAX_SNAPSHOTS` - - Maximum number of canister snapshots per canister. - -- `MAX_CALL_TIMEOUT` - - The maximum timeout (in seconds) for an inter-canister call. - -- `MAX_ENV_VAR_NAME_LENGTH` - - The maximum length of an environment variable name. - -- `MAX_ENV_VAR_VALUE_LENGTH` - - The maximum length of an environment variable value. - -- `MAX_ENV_VAR_COUNT` - - The maximum number of environment variables allowed. - -### Principals {#principal} - -Principals are generic identifiers for canisters, users and possibly other concepts in the future. As far as most uses of the IC are concerned they are *opaque* binary blobs with a length between 0 and 29 bytes, and there is intentionally no mechanism to tell canister ids and user ids apart. - -There is, however, some structure to them to encode specific authentication and authorization behavior. - -#### Special forms of Principals {#id-classes} - -In this section, `H` denotes SHA-224, `·` denotes blob concatenation and `|p|` denotes the length of `p` in bytes, encoded as a single byte. - -There are several classes of ids: - -1. *Opaque ids*. - - These are always generated by the IC and have no structure of interest outside of it. - -:::note - -Typically, these end with the byte `0x01`, but users of the IC should not need to care about that. - -::: - -2. *Self-authenticating ids*. - - These have the form `H(public_key) · 0x02` (29 bytes). - - An external user can use these ids as the `sender` of a request if they own the corresponding private key. The public key uses one of the encodings described in [Signatures](#signatures). - -3. *Derived ids* - - These have the form `H(|registering_principal| · registering_principal · derivation_nonce) · 0x03` (29 bytes). - - These ids are treated specially when an id needs to be registered. In such a request, whoever requests an id can provide a `derivation_nonce`. By hashing that together with the principal of the caller, every principal has a space of ids that only they can register ids from. - -:::note - -Derived IDs are currently not explicitly used in this document, but they may be used internally or in the future. - -::: - -4. *Anonymous id* - - This has the form `0x04`, and is used for the anonymous caller. It can be used in call and query requests without a signature. - -5. *Reserved ids* - - These have the form of `blob · 0x7f`, `0 ≤ |blob| < 29`. - - These ids can be useful for applications that want to re-use the [Textual representation of principals](#textual-ids) but want to indicate explicitly that the blob does not address any canisters or a user. - -When the IC creates a *fresh* id, it never creates a self-authenticating id, reserved id, an anonymous id or an id derived from what could be a canister or user. - -#### Textual representation of principals {#textual-ids} - -We specify a *canonical textual format* that is recommended whenever principals need to be printed or read in textual format, e.g. in log messages, transactions browser, command line tools, source code. - -The textual representation of a blob `b` is `Grouped(Base32(CRC32(b) · b))` where - -- `CRC32` is a four byte check sequence, calculated as defined by ISO 3309, ITU-T V.42, and [elsewhere](https://www.w3.org/TR/2003/REC-PNG-20031110/#5CRC-algorithm), and stored as big-endian, i.e., the most significant byte comes first and then the less significant bytes come in descending order of significance (MSB B2 B1 LSB). - -- `Base32` is the Base32 encoding as defined in [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648#section-6), with no padding character added. - -- The middle dot denotes concatenation. - -- `Grouped` takes an ASCII string and inserts the separator `-` (dash) every 5 characters. The last group may contain less than 5 characters. A separator never appears at the beginning or end. - -The textual representation is conventionally printed with *lower case letters*, but parsed case-insensitively. - -Because the maximum size of a principal is 29 bytes, the textual representation will be no longer than 63 characters (10 times 5 plus 3 characters with 10 separators in between them). - -:::tip - -The canister with id `0xABCD01` has check sequence `0x233FF206` ([online calculator](https://crccalc.com/?crc=ABCD01&method=crc32&datatype=hex&outtype=hex)); the final id is thus `em77e-bvlzu-aq`. - -Example encoding from hex, and decoding to hex, in bash (the following can be pasted into a terminal as is): -``` -function textual_encode() { - ( echo "$1" | xxd -r -p | /usr/bin/crc32 /dev/stdin; echo -n "$1" ) | - xxd -r -p | base32 | tr A-Z a-z | - tr -d = | fold -w5 | paste -sd'-' - -} - -function textual_decode() { - echo -n "$1" | tr -d - | tr a-z A-Z | - fold -w 8 | xargs -n1 printf '%-8s' | tr ' ' = | - base32 -d | xxd -p | tr -d '\n' | cut -b9- | tr a-z A-Z -} -``` - -::: - -### Canister lifecycle {#canister-lifecycle} - -Dapps on the Internet Computer are called *canisters*. Conceptually, they consist of the following pieces of state: - -- A canister id (a [principal](#principal)) - -- Their *controllers* (a possibly empty list of [principal](#principal)) - -- A cycle balance - -- A reserved cycles balance, which are cycles set aside from the main cycle balance for resource payments. - -- The *canister status*, which is one of `running`, `stopping` or `stopped`. - -- Resource reservations - -A canister can be *empty* (e.g. directly after creation) or *non-empty*. A non-empty canister also has - -- code, in the form of a canister module - -- memories (heap and stable memory) - -- globals - -- possibly further data that is specific to the implementation of the IC (e.g. queues) - -Canisters are empty after creation and uninstallation, and become non-empty through [code installation](#ic-install_code). - -If an empty canister receives a response, that response is dropped, as if the canister trapped when processing the response. The cycles set aside for its processing and the cycles carried on the responses are added to the canister's *cycles* balance. - -#### Canister cycles {#canister-cycles} - -The IC relies on *cycles*, a utility token, to manage its resources. A canister pays for the resources it uses from its *cycle balances*. A *cycle\_balance* is stored as 128-bit unsigned integers and operations on them are saturating. In particular, if *cycles* are added to a canister that would bring its main cycle balance beyond 2128-1, then the balance will be capped at 2128-1 and any additional cycles will be lost. - -When both the main and the reserved cycles balances of a canister fall to zero, the canister is *deallocated*. This has the same effect as - -- uninstalling the canister (as described in [IC method](#ic-uninstall_code)) - -- setting all resource reservations to zero - -Afterwards the canister is empty. It can be reinstalled after topping up its main balance. - -:::note - -Once the IC frees the resources of a canister, its id, *cycle* balances, *controllers*, canister *version*, and the total number of canister changes are preserved on the IC for a minimum of 10 years. What happens to the canister after this period is currently unspecified. - -::: - -#### Canister status {#canister-status} - -The canister status can be used to control whether the canister is processing calls: - -- In status `running`, calls to the canister are processed as normal. - -- In status `stopping`, calls to the canister are rejected by the IC with reject code `CANISTER_ERROR` (5), but responses to the canister are processed as normal. - -- In status `stopped`, calls to the canister are rejected by the IC with reject code `CANISTER_ERROR` (5), and there are no outstanding responses. - -In all cases, calls to the [management canister](#ic-management-canister) are processed, regardless of the state of the managed canister. - -The controllers of the canister or subnet admins can initiate transitions between these states using [`stop_canister`](#ic-stop_canister) and [`start_canister`](#ic-start_canister), and query the state using [`canister_status`](#ic-canister_status) (NB: this call returns additional information, such as the cycle balance of the canister). The canister itself can also query its state using [`ic0.canister_status`](#system-api-canister-status). - -:::note - -This status is orthogonal to whether a canister is empty or not: an empty canister can be in status `running`. Calls to such a canister are still rejected by the IC, but because the canister is empty. - -::: - -:::note - -This status is orthogonal to whether a canister is frozen or not: a frozen canister can be in status `running`. Calls to such a canister are still rejected by the IC, but because the canister is frozen, the returned reject code is `SYS_TRANSIENT`. - -::: - -:::note - -If a canister is in the `stopped` state, an additional boolean may be of interest: `ready_for_migration` indicates whether a stopped canister is ready to be migrated to another subnet (i.e., whether it has empty queues and flushed streams). This flag can only ever be `true` if the `status` is `stopped`. This property is guaranteed by the protocol, but deliberately not on the type level in order to facilitate backwards compatible service evolution. - -::: - -### Signatures {#signatures} - -Digital signature schemes are used for authenticating messages in various parts of the IC infrastructure. Signatures are domain separated, which means that every message is prefixed with a byte string that is unique to the purpose of the signature. - -The IC supports multiple signature schemes, with details given in the following subsections. For each scheme, we specify the data encoded in the public key (which is always DER-encoded, and indicates the scheme to use) as well as the form of the signatures (which are opaque blobs for the purposes of the rest of this specification). - -In all cases, the signed *payload* is the concatenation of the domain separator and the message. All uses of signatures in this specification indicate a domain separator, to uniquely identify the purpose of the signature. The domain separators are prefix-free by construction, as their first byte indicates their length. - -#### Ed25519 and ECDSA signatures {#ecdsa} - -Plain signatures are supported for the schemes - -- [**Ed25519**](https://ed25519.cr.yp.to/index.html) or - -- [**ECDSA**](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) on curve P-256 (also known as `secp256r1`), using SHA-256 as hash function, as well as on the Koblitz curve `secp256k1`. - -- Public keys must be valid for signature schemes Ed25519 or ECDSA and are encoded as DER. - - - See [RFC 8410](https://datatracker.ietf.org/doc/html/rfc8410) for DER encoding of Ed25519 public keys. - - - See [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480) for DER encoding of ECDSA public keys; the DER encoding must not specify a hash function. For curve `secp256k1`, the OID 1.3.132.0.10 is used. The points must be specified in uncompressed form (i.e. `0x04` followed by the big-endian 32-byte encodings of `x` and `y`). - -- The signatures are encoded as the concatenation of the 32-byte big endian encodings of the two values *r* and *s*. - -#### Web Authentication {#webauthn} - -The allowed signature schemes for web authentication are - -- [**ECDSA**](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) on curve P-256 (also known as `secp256r1`), using SHA-256 as hash function. - -- [**RSA PKCS\#1v1.5 (RSASSA-PKCS1-v1\_5)**](https://datatracker.ietf.org/doc/html/rfc8017#section-8.2), using SHA-256 as hash function. - -The signature is calculated by using the payload as the challenge in the web authentication assertion. - -The signature is checked by verifying that the `challenge` field contains the [base64url encoding](https://datatracker.ietf.org/doc/html/rfc4648#section-5) of the payload, and that `signature` verifies on `authenticatorData · SHA-256(utf8(clientDataJSON))`, as specified in the [WebAuthn w3c recommendation](https://www.w3.org/TR/webauthn/#op-get-assertion). - -- The public key is encoded as a DER-wrapped COSE key. - - It uses the `SubjectPublicKeyInfo` type used for other types of public keys (see, e.g., [RFC 8410, Section 4](https://datatracker.ietf.org/doc/html/rfc8410#section-4)), with OID 1.3.6.1.4.1.56387.1.1 (iso.org.dod.internet.private.enterprise.dfinity.mechanisms.der-wrapped-cose). The `BIT STRING` field `subjectPublicKey` contains the COSE encoding. See [WebAuthn w3c recommendation](https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples) or [RFC 8152](https://datatracker.ietf.org/doc/html/rfc8152#section-13.1) for details on the COSE encoding. - -:::tip - -A DER wrapping of a COSE key is shown below. It can be parsed via the command `sed "s/#.*//" | xxd -r -p | openssl asn1parse -inform der`. - - 30 5E # SEQUENCE of length 94 bytes - 30 0C # SEQUENCE of length 12 bytes - 06 0A 2B 06 01 04 01 83 B8 43 01 01 # OID 1.3.6.1.4.1.56387.1.1 - 03 4E 00 # BIT STRING encoding of length 78, - A501 0203 2620 0121 5820 7FFD 8363 2072 # length is at byte boundary - FD1B FEAF 3FBA A431 46E0 EF95 C3F5 5E39 # contents is a valid COSE key - 94A4 1BBF 2B51 74D7 71DA 2258 2032 497E # with ECDSA on curve P-256 - ED0A 7F6F 0009 2876 5B83 1816 2CFD 80A9 - 4E52 5A6A 368C 2363 063D 04E6 ED - -You can also view the wrapping in [an online ASN.1 JavaScript decoder](https://lapo.it/asn1js/#MF4wDAYKKwYBBAGDuEMBAQNOAKUBAgMmIAEhWCB__YNjIHL9G_6vP7qkMUbg75XD9V45lKQbvytRdNdx2iJYIDJJfu0Kf28ACSh2W4MYFiz9gKlOUlpqNowjYwY9BObt). - -::: - -- The signature is a CBOR (see [CBOR](#cbor)) value consisting of a data item with major type 6 ("Semantic tag") and tag value `55799`, followed by a map with three mandatory fields: - - - `authenticator_data` (`blob`): WebAuthn authenticator data. - - - `client_data_json` (`text`): WebAuthn client data in JSON representation. - - - `signature` (`blob`): Signature as specified in the [WebAuthn w3c recommendation](https://www.w3.org/TR/webauthn/#signature-attestation-types), which means DER encoding in the case of an ECDSA signature. - -#### Canister signatures {#canister-signatures} - -The IC also supports a scheme where a canister can sign a payload by declaring a special "certified variable". - -This section makes forward references to other concepts in this document, in particular the section [Certification](#certification). - -- The public key is a DER-wrapped structure that indicates the *signing canister*, and includes a freely choosable seed. Each choice of seed yields a distinct public key for the canister, and the canister can choose to encode information, such as a user id, in the seed. - - More concretely, it uses the `SubjectPublicKeyInfo` type used for other types of public keys (see, e.g., [RFC 8410, Section 4](https://datatracker.ietf.org/doc/html/rfc8410#section-4)), with OID 1.3.6.1.4.1.56387.1.2 (iso.org.dod.internet.private.enterprise.dfinity.mechanisms.canister-signature). - - The `BIT STRING` field `subjectPublicKey` is the blob `|signing_canister_id| · signing_canister_id · seed`, where `|signing_canister_id|` is the one-byte encoding of the the length of the `signing_canister_id` and `·` denotes blob concatenation. - -- The signature is a CBOR (see [CBOR](#cbor)) value consisting of a data item with major type 6 ("Semantic tag") and tag value `55799`, followed by a map with two mandatory fields: - - - `certificate` (`blob`): A CBOR-encoded certificate as per [Encoding of certificates](#certification-encoding). - - - `tree` (`hash-tree`): A hash tree as per [Encoding of certificates](#certification-encoding). - -- Given a payload together with public key and signature in the format described above the signature can be verified by checking the following two conditions: - - - The `certificate` must be a valid certificate as described in [Certification](#certification), with - ``` - lookup_path(["canister", , "certified_data"], certificate.tree) = Found (reconstruct(tree)) - ``` - - where `signing_canister_id` is the id of the signing canister and `reconstruct` is a function that computes a root-hash for the tree. - - - If the `certificate` includes a subnet delegation, then the `signing_canister_id` must be included in the delegation's canister id range (see [Delegation](#certification-delegation)). - - - The `tree` must be a `well_formed` tree with - ``` - lookup_path(["sig", , ], tree) = Found "" - ``` - - where `s` is the SHA-256 hash of the `seed` used in the public key and `m` is the SHA-256 hash of the payload. - -### Supplementary Technologies {#supplementary-technologies} - -#### CBOR {#cbor} - -[Concise Binary Object Representation (CBOR)](https://www.rfc-editor.org/rfc/rfc8949) is a data format with a small code footprint, small message size and an extensible interface. CBOR is used extensively throughout the Internet Computer as the primary format for data exchange between components within the system. - -[cbor.io](https://cbor.io) and [wikipedia.org](https://en.wikipedia.org/wiki/CBOR) contain a lot of helpful background information and relevant tools. [cbor.me](https://cbor.me) in particular, is very helpful for converting between CBOR hex and diagnostic information. - -For example, the following CBOR hex: -``` -82 61 61 a1 61 62 61 63 -``` - -Can be converted into the following CBOR diagnostic format: -``` -["a", {"b": "c"}] -``` - -Particular concepts to note from the spec are: - -- [Specification of the CBOR Encoding](https://www.rfc-editor.org/rfc/rfc8949#name-specification-of-the-cbor-e) - -- [CBOR Major Types](https://www.rfc-editor.org/rfc/rfc8949#name-major-types) - -- [CBOR Self-Describe](https://www.rfc-editor.org/rfc/rfc8949#self-describe) - -#### CDDL {#cddl} - -The [Concise Data Definition Language (CDDL)](https://datatracker.ietf.org/doc/html/rfc8610) is a data description language for CBOR. It is used at various points throughout this document to describe how certain data structures are encoded with CBOR. - -## The system state tree {#state-tree} - -Parts of the IC state are publicly exposed (e.g. via [Request: Read state](#http-read-state) or [Certified data](#system-api-certified-data)) in a verified way (see [Certification](#certification) for the machinery for certifying). This section describes the content of this system state abstractly. - -Conceptually, the system state is a tree with labeled children, and values in the leaves. Equivalently, the system state is a mapping from paths (sequences of labels) to values, where the domain is prefix-free. - -Labels are always blobs (but often with a human readable representation). In this document, paths are written suggestively with slashes as separators; the actual encoding is not actually using slashes as delimiters, and labels may contain the 0x2F byte (ASCII `/`) just fine. Values are either natural numbers, text values or blob values. - -This section specifies the publicly relevant paths in the tree. - -### Time {#state-tree-time} - -- `/time` (natural): - - All partial state trees include a timestamp, expressed in nanoseconds since 1970-01-01, indicating the time at which the state is current. - -### Api boundary nodes information {#state-tree-api-bn} - -The state tree contains information about all API boundary nodes (the source of truth for these API boundary node records is stored in the NNS registry canister). - -- `/api_boundary_nodes//domain` (text) - - Domain name associated with a node. All domains are unique across nodes. - 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`. - -- `/api_boundary_nodes//ipv6_address` (text) - - Public IPv6 address of a node in the hexadecimal notation with colons. - Example: `3002:0bd6:0000:0000:0000:ee00:0033:6778`. - -### Subnet information {#state-tree-subnet} - -The state tree contains information about the topology of the Internet Computer. - -- `/subnet//public_key` (blob) - - The public key of the subnet (a DER-encoded BLS key, see [Certification](#certification)) - -- `/subnet//type` (text) - - The subnet type of the subnet. Possible values are "application", "system", "verified_application", and "cloud_engine" (without quotes). - -- `/subnet//canister_ranges` (blob) - - The set of canister ids assigned to this subnet, represented as a list of closed intervals of canister ids, ordered lexicographically, and encoded as CBOR (see [CBOR](#cbor)) according to this CDDL (see [CDDL](#cddl)): - ``` - canister_ranges = tagged<[*canister_range]> - canister_range = [principal principal] - principal = bytes .size (0..29) - tagged = #6.55799(t) ; the CBOR tag - ``` - -- `/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 - -Because this uses the lexicographic ordering of principals, and the byte distinguishing the various classes of ids is at the *end*, this range by construction conceptually includes principals of various classes. This specification needs to take care that the fact that principals that are not canisters may appear in these ranges does not cause confusion. - -::: - -- `/subnet//node//public_key` (blob) - - The public key of a node (a DER-encoded Ed25519 signing key, see [RFC 8410](https://tools.ietf.org/html/rfc8410) for reference) with principal `` belonging to the subnet with principal ``. - - -### Canister ranges {#state-tree-canister-ranges} - -The state tree also stores the canister ID ranges of subnets on the Internet Computer in a sharded form. - -- `/canister_ranges//` (blob) - - The set of canister IDs assigned to this subnet is represented as a **list of closed intervals of canister IDs, ordered lexicographically**. - This list is then split into **non-overlapping shards**, with each shard stored under a path of the above form and encoded as CBOR (see [CBOR](#cbor)). - - Specifically: - 1. Each shard contains a non-empty list of ranges. - 2. The first range in the shard starts with the `` in its path. - 3. The next shard (if any) begins with a strictly greater starting canister ID. - 4. All shards together cover the entire set of canister ID ranges for the subnet without overlap. - - **Example:** Suppose a subnet has these canister ID ranges: - ``` - [1, 3], [5, 8], [10, 12], [20, 25] - ``` - They could be split into two shards: - - `/canister_ranges//1` → `[1, 3], [5, 8]` - - `/canister_ranges//10` → `[10, 12], [20, 25]` - - Each shard is represented as a CBOR-encoded list of ranges. - The encoding follows the same CDDL (see [CDDL](#cddl)) as for subnet-level canister ranges: - - ``` - canister_ranges = tagged<[*canister_range]> ; unlike before, this now represents a single shard - canister_range = [principal principal] - principal = bytes .size (0..29) - tagged = #6.55799(t) ; the CBOR tag - ``` - - **Difference from `/subnet//canister_ranges`:** - - `/subnet//canister_ranges` stores the complete set of ranges in one blob. - - `/canister_ranges//` stores the same ranges split into consecutive shards, each identified by its starting `` in the path. This facilitates e.g., binary searching. - -### Request status {#state-tree-request-status} - -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) - - One of `received`, `processing`, `replied`, `rejected` or `done`, see [Overview of canister calling](#http-call-overview) for more details on what each status means. - -- `/request_status//reply` (blob) - - If the status is `replied`, then this path contains the reply blob, else it is not present. - -- `/request_status//reject_code` (natural) - - If the status is `rejected`, then this path contains the reject code (see [Reject codes](#reject-codes)), else it is not present. - -- `/request_status//reject_message` (text) - - If the status is `rejected`, then this path contains a textual diagnostic message, else it is not present. - -- `/request_status//error_code` (text) - - If the status is `rejected`, then this path might be present and contain an implementation-specific error code (see [Error codes](#error-codes)), else it is not present. - -:::note - -Immediately after submitting a request, the request may not show up yet as the Internet Computer is still working on accepting the request as pending. - -::: - -:::note - -Request statuses will not actually be kept around indefinitely, and eventually the Internet Computer forgets about the request. This will happen no sooner than the request's expiry time, so that replay attacks are prevented. - -::: - -### Certified data {#state-tree-certified-data} - -- `/canister//certified_data` (blob): - - The certified data of the canister with the given id, see [Certified data](#system-api-certified-data). - -### Canister information {#state-tree-canister-information} - -Users have the ability to learn about the hash of the canister's module, its current controllers, and metadata in a certified way. - -- `/canister//module_hash` (blob): - - If the canister is empty, this path does not exist. If the canister is not empty, it exists and contains the SHA256 hash of the currently installed canister module. Cf. [IC method](#ic-canister_status). - -- `/canister//controllers` (blob): - - The current controllers of the canister. The value consists of a CBOR (see [CBOR](#cbor)) data item with major type 6 ("Semantic tag") and tag value `55799`, followed by an array of principals in their binary form (CDDL `#6.55799([* bytes .size (0..29)])`, see [CDDL](#cddl)). - -- `/canister//metadata/` (blob): - - If the canister has a [custom section](https://webassembly.github.io/spec/core/binary/modules.html#custom-section) called `icp:public ` or `icp:private `, this path contains the content of the custom section. Otherwise, this path does not exist. - - It is recommended for the canister to have a custom section called "icp:public candid:service", which contains the UTF-8 encoding of [the Candid interface](https://github.com/dfinity/candid/blob/master/spec/Candid.md#core-grammar) for the canister. - -## HTTPS Interface {#http-interface} - -The concrete mechanism that users use to send requests to the Internet Computer is via an HTTPS API, which exposes four endpoints to handle interactions, plus one for diagnostics: - -- At `/api/v2/canister//call`, the user can submit update calls that are asynchronous and might change the IC state. - -- At `/api/v3/canister//call` (deprecated) and `/api/v4/canister//call`, the user can submit update calls and get a synchronous HTTPS response with a certificate for the call status. - -- At `/api/v2/canister//read_state` (deprecated), `/api/v2/subnet//read_state` (deprecated), `/api/v3/canister//read_state`, and `/api/v3/subnet//read_state`, the user can read various information about the state of the Internet Computer. In particular, they can poll for the status of a call here. - -- At `/api/v2/canister//query` (deprecated) and `/api/v3/canister//query`, the user can perform (synchronous, non-state-changing) query calls. - -- At `/api/v2/status` the user can retrieve status information about 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/.../call`, `/api/.../read_state`, and `/api/.../query` are POST requests with a CBOR-encoded request body, which consists of a authentication envelope (as per [Authentication](#authentication)) and request-specific content as described below. - -:::note - -This document does not yet explain how to find the location and port of the Internet Computer. - -::: - -### 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. - -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) and the call is received by a replica (a node belonging to an IC subnet). The receiving replica decides whether it accepts the call. An honest replica does so by checking that the target canister is not frozen and - - - checking that the target canister is not empty, checking that the target canister is running, and performing [ingress message inspection](#system-api-inspect-message) for calls to a regular canister; - - - checking that the management canister method can be called via ingress messages and that the caller is a controller of the target canister for calls to the management canister - (or that the call targets the [IC Provisional API](#ic-provisional-api) on a development instance). - - Moreover, the signature must be valid and created with a correct key. - - Finally, the system time (of the replica receiving the HTTP request) must not have exceeded the `ingress_expiry` field of the HTTP request containing the call. - - From this point on the user may receive a response from the IC about the status of the call. Only valid IC certificates in responses should be trusted, since responses come from a single replica that can be either honest or malicious. Note that a lack of a valid IC certificate doesn't necessarily mean that the responding replica is malicious; examples of responses that are expected to come without a certificate (and thus aren't necessarily trustworthy) include responses signalling that the message hasn't been accepted, and responses saying that the request is accepted for further processing. - - So far the corresponding IC subnet (as a whole) still behaves as if it does not know about the call. - - At some point, the IC subnet (as a whole) receives the call and sets its (certified) status to `received`. - - The above steps are formalized in this [transition](#api-request-submission). - -2. Once the IC starts processing the call, its (certified) status is set to `processing`. This transition can only happen before the target canister's time (as visible in the [state tree](#state-tree-time)) exceeds the [`ingress_expiry`](#http-call) field of the HTTP request which contained the call. Now the user has the guarantee that the call will have some effect. - -3. The IC is processing the call. For some calls this may be atomic, for others this involves multiple steps. - -4. Eventually, a response is produced and available in the (certified) [state tree](#state-tree-request-status) from which it can be retrieved for a certain amount of time. The response is either a `reply`, indicating success, or a `reject`, indicating some form of error. - -5. In case of high load on the IC, even if the call has not expired yet, the IC can forget the response data and only remember the call as `done`, to prevent a replay attack. - -6. Once the call's expiry time has passed, the IC can remove the call and its response from the (certified) [state tree](#state-tree-request-status) and thus completely forget about it. - -This yields the following interaction diagram: -```plantuml - (*) --> "User creates call" #DDDDDD - --> "Submitted to node\n(with 202 response)" as submit #DDDDDD - --> "received" - --> "processing" - if "" as X then - --> "replied" - --> "done" - else - --> "rejected (canister)" - --> "done" - - "X" --> "rejected (system)" - "received" --> "rejected (system)" - --> "done" - - "received" --> "pruned" #DDDDDD - "submit" --> "dropped" #DDDDDD - "done" --> "pruned" #DDDDDD - - endif -``` -State transitions may be instantaneous and not always externally visible. For example, the state of a request may move from `received` via `processing` to `replied` in one go. Similarly, the IC may not implement the `done` state at all, and keep calls in state `replied`/`rejected` until they are pruned. - -All gray states are *not* explicitly represented in the state of the IC, and are indistinguishable from "call does not exist". - -The characteristic property of the `received` state is that the call has made it past the (potentially malicious) endpoint *into the state of the IC*. It is now pointless (but harmless) to submit the (identical) call again. Before reaching that state, submitting the identical call to further nodes might be a useful safeguard against a malicious or misbehaving node. - -The characteristic property of the `processing` state is that *the initial effect of the call has happened or will happen*. This is best explained by an example: Consider a counter canister. It exports a method `inc` that increases the counter. Assume that the canister is bug free, and is not going to be forcibly removed. A user submits a call to call `inc`. If the user sees request status `processing`, the state change is guaranteed to happen. The user can stop monitoring the status and does not have to retry submitting. - -A call may be rejected by the IC or the canister. In either case, there is no guarantee about how much processing of the call has happened. - -To avoid replay attacks, the transition from `done` or `received` to `pruned` must happen no earlier than the call's `ingress_expiry` field. -If a subnet's time strictly exceeds the call's `ingress_expiry` field, the subnet's time exceeds the call's `ingress_expiry` field by at most 5 minutes, and the call's status is unknown to the IC (i.e., it was never in state `received`, `processing`, `replied`, `rejected`, or `done`), then the call will never be in one of these states. - -Calls should stay in `replied` or `rejected` for 5 minutes so that polling users can catch the response under good networking conditions -and low load on the IC. However, in case of high load on the IC, the IC can transition the call to `done` at any time. - -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. -On the replica, a synchronous call request goes through the same states (`received`, `processing`, `replied`, `rejected`, or `done`) as the ones depicted for the [asynchronous API](#http-async-call-overview). -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 reduces 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` (deprecated) or `/api/v4/canister//call`. The request body consists of an authentication envelope with a `content` map with the following fields: - -- `request_type` (`text`): Always `call` - -- `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. - -- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). The canister will not start processing a call past its `ingress_expiry`. - -- `sender_info` (`map`, optional): Map with fields: - - - `info` (`blob`, required): The sender information passed to the canister. - - - `signer` (`blob`, required): The principal of the signing canister. This must be equal to the canister ID encoded in the `sender_pubkey`, i.e. the `signing_canister_id` component of the canister signature public key, as described in [canister signature](#canister-signatures). - - - `sig` (`blob`, required): Signature to authenticate the `info` field. This signature *must* be a [canister signature](#canister-signatures), using the 15 bytes `\x0Eic-sender-info` as the domain separator for the payload, and *must* verify using `sender_pubkey` as the canister signature public key. - -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`): `"replied"` - - - `certificate` (`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. - -If the `certificate` includes a subnet delegation (see [Delegation](#certification-delegation)), then - -- for requests to `/api/v3/canister//call`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at the path of the form `/subnet//canister_ranges`, - -- for requests to `/api/v4/canister//call`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at a path with prefix `/canister_ranges/`. - -This request type can *also* be used to call a query method (but not a composite query method). A user may choose to go this way, instead of via the faster and cheaper [Request: Query call](#http-query) below, if they want to get a *certified* response. Note that the canister state will not be changed by sending a call request type for a query method (except for transient state such as cycle balance, canister logs, and canister version). - -### 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` - -- `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 - -- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). The canister will not start processing a call past its `ingress_expiry`. - -- `sender_info` (`map`, optional): Map with fields: - - - `info` (`blob`, required): The sender information passed to the canister. - - - `signer` (`blob`, required): The principal of the signing canister. This must be equal to the canister ID encoded in the `sender_pubkey`, i.e. the `signing_canister_id` component of the canister signature public key, as described in [canister signature](#canister-signatures). - - - `sig` (`blob`, required): Signature to authenticate the `info` field. This signature *must* be a [canister signature](#canister-signatures), using the 15 bytes `\x0Eic-sender-info` as the domain separator for the payload, and *must* verify using `sender_pubkey` as the canister signature public key. - -The HTTP response to this request can have the following responses: - -- 202 HTTP status with empty body. Implying the request was accepted by the IC for further processing. Users should use [`read_state`](#http-read-state) to determine the status of the call. - -- 200 HTTP status with non-empty body. Implying an execution pre-processing error occurred. The body of the response contains more information about the IC specific error encountered. The body is a CBOR map with the following fields: - - - `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)). - -- 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 transient state such as cycle balance, canister logs, and canister version). - -:::note - -The functionality exposed via the [The IC management canister](#ic-management-canister) can be used this way. - -::: - -### Request: Read state {#http-read-state} - -:::note - -Requesting paths with the prefix `/canister_ranges` and `/subnet` at `/api/v3/canister//read_state` might be deprecated in the future. Hence, users might want to point their requests for paths with the prefix `/canister_ranges` and `/subnet` to `/api/v3/subnet//read_state`. - -On the IC mainnet, the root subnet ID `tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe` can be used to retrieve the list of all IC mainnet's subnets by requesting the prefix `/subnet` at `/api/v3/subnet/tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe/read_state`. - -::: - -In order to read parts of the [The system state tree](#state-tree), the user makes a POST request to `/api/v2/canister//read_state` (deprecated), `/api/v2/subnet//read_state` (deprecated), `/api/v3/canister//read_state`, or `/api/v3/subnet//read_state`. The subnet form should be used when the information to be retrieved is subnet specific, i.e., when requesting paths with the prefix `/time`, `/canister_ranges`, or `/subnet`, and the subnet form must be used when requesting paths of the form `/subnet//metrics`. The request body consists of an authentication envelope with a `content` map with the following fields: - -- `request_type` (`text`): Always `read_state` - -- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). `ingress_expiry` refers to this request's expiry, not the expiry of any call request referenced in this read state request. - -- `paths` (sequence of paths): A list of at most 1000 paths, where a path is itself a sequence of at most 127 blobs. - -The HTTP response to this request can have the following forms: - -- 200 HTTP status with a non-empty body consisting of a CBOR (see [CBOR](#cbor)) map with the following fields: - - - `certificate` (`blob`): A certificate (see [Certification](#certification)). - -- 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. - -In the following, we list properties of the returned certificate and specify conditions on the requested paths. - -If the `certificate` includes a subnet delegation (see [Delegation](#certification-delegation)), then - -- for requests to `/api/v2/canister//read_state`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at the path of the form `/subnet//canister_ranges`, - -- for requests to `/api/v3/canister//read_state`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at a path with prefix `/canister_ranges/`, - -- for requests to `/api/v2/subnet//read_state` and `/api/v3/subnet//read_state`, the `` must match the delegation's subnet id. - -The returned certificate reveals all values whose path has a requested path as a prefix except for - -- paths with prefix `/subnet//canister_ranges` which are only contained in the returned certificate - - - for requests to `/api/v2/canister//read_state` (deprecated) and `/api/v2/subnet//read_state` (deprecated); - - - if `` is the root subnet ID; - -- paths with prefix `/subnet//metrics` and `/subnet//node` which are only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to the value at a path with prefix `/canister_ranges/` in the state tree, or if `` matches ``. - -The returned certificate also always reveals `/time`, even if not explicitly requested. - -:::note - -The returned certificate might also reveal the SHA-256 hashes of values whose paths have not been requested -and whose paths might not even be allowed to be requested by the sender of the HTTP request. -This means that unauthorized users might obtain the SHA-256 hashes of ingress message responses -and private custom sections of the canister's module. -Hence, users are advised to use cryptographically strong nonces in their HTTP requests and -canister developers that aim at keeping data confidential are advised to add a secret cryptographic salt to their canister's responses and private custom sections. - -::: - -All requested paths must have the following form: - -- `/time`. Can always be requested. - -- `/api_boundary_nodes`, `/api_boundary_nodes/`, `/api_boundary_nodes//domain`, `/api_boundary_nodes//ipv4_address`, `/api_boundary_nodes//ipv6_address`. Can always be requested. - -- `/canister_ranges/`. Can only be requested at `/api/v2/subnet//read_state` and `/api/v3/subnet//read_state`. Cannot be requested at `/api/v2/canister//read_state` and `/api/v3/canister//read_state`. - -- `/subnet`, `/subnet/`, `/subnet//public_key`, `/subnet//type`, `/subnet//node`, `/subnet//node/`, `/subnet//node//public_key`. Can always be requested. - -- `/subnet//canister_ranges`, where `` is the root subnet ID. Can always be requested. - -- `/subnet//canister_ranges`, where `` is not the root subnet ID. Can be requested at `/api/v2/canister//read_state` and `/api/v2/subnet//read_state`. Cannot be requested at `/api/v3/canister//read_state` and `/api/v3/subnet//read_state`. - -- `/subnet//metrics`. Can only be requested at `/api/v2/subnet//read_state` and `/api/v3/subnet//read_state` (i.e., if `` matches ``). In particular, cannot be requested at `/api/v2/canister//read_state` and `/api/v3/canister//read_state`. - -- `/request_status/`, `/request_status//status`, `/request_status//reply`, `/request_status//reject_code`, `/request_status//reject_message`, `/request_status//error_code`. Can be requested if no path with such a prefix exists in the state tree or - - - the sender of the original request referenced by `` is the same as the sender of the read state request and - - - the effective canister id of the original request referenced by `` matches ``. - -- `/canister//module_hash`. Can be requested if `` matches ``. - -- `/canister//controllers`. Can be requested if `` matches ``. The order of controllers in the value at this path may vary depending on the implementation. - -- `/canister//metadata/`. Can be requested if `` matches ``, `` is encoded in UTF-8, and - - - canister with canister id `` does not exist or - - - canister with canister id `` is empty or - - - canister with canister id `` does not have `` as its custom section or - - - `` is a public custom section or - - - `` is a private custom section and the sender of the read state request is a controller of the canister. - -Moreover, - -- all paths with prefix `/request_status/` must refer to the same request ID ``; and - -- all paths with prefix `/canister_ranges/` must refer to the same subnet ID ``. - -If a path cannot be requested, then the HTTP response to the read state request is undefined. - -Note that the paths `/canister//certified_data` are not accessible with this method; these paths are only exposed to the canisters themselves via the System API (see [Certified data](#system-api-certified-data)). - -See [The system state tree](#state-tree) for details on the state tree. - -### Request: Query call {#http-query} - -A query call is a fast, but less secure way to call canister methods that do not change the canister state. -Only methods that are explicitly marked as "query methods" and "composite query methods" by the canister can be called this way. -In contrast to a query method, a composite query method can make further calls to query and composite query methods of canisters on the same subnet. - -The following limits apply to the evaluation of a query call: - -- The amount of cycles that are used in total (across all calls to query and composite query methods and their callbacks) during evaluation of a query call is at most `MAX_CYCLES_PER_QUERY`. - -- The maximum nesting level of calls during evaluation of a query call is at most `MAX_CALL_DEPTH_COMPOSITE_QUERY`. - -- The wall clock time spent on evaluation of a query call is at most `MAX_WALL_CLOCK_TIME_COMPOSITE_QUERY`. - -In order to make a query call to a canister, the user makes a POST request to `/api/v2/canister//query` (deprecated) or `/api/v3/canister//query`. The request body consists of an authentication envelope with a `content` map with the following fields: - -- `request_type` (`text`): Always `"query"`. - -- `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. - -- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). - -- `sender_info` (`map`, optional): Map with fields: - - - `info` (`blob`, required): The sender information passed to the canister. - - - `signer` (`blob`, required): The principal of the signing canister. This must be equal to the canister ID encoded in the `sender_pubkey`, i.e. the `signing_canister_id` component of the canister signature public key, as described in [canister signature](#canister-signatures). - - - `sig` (`blob`, required): Signature to authenticate the `info` field. This signature *must* be a [canister signature](#canister-signatures), using the 15 bytes `\x0Eic-sender-info` as the domain separator for the payload, and *must* verify using `sender_pubkey` as the canister signature public key. - -The HTTP response to this request can have the following forms: - -- 200 HTTP status with a non-empty body consisting of a CBOR (see [CBOR](#cbor)) map with the following fields: - - - `status` (`text`): `"replied"` - - - `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data. - - - `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response. - -- 200 HTTP status with a non-empty body consisting of a CBOR (see [CBOR](#cbor)) map with the following fields: - - - `status` (`text`): `"rejected"` - - - `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)). - - - `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response. - -- 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. - -:::note - -Although `signatures` only contains one node signature, we still declare its type to be a list to prevent future breaking changes -if we include more signatures in a future version of the protocol specification. - -::: - -A successful response to a query call (200 HTTP status) contains a list with one signature for the returned response produced by the IC node that evaluated the query call. The signature (whose type is denoted as `node-signature`) is a CBOR (see [CBOR](#cbor)) map with the following fields: - -- `timestamp` (`nat`): the timestamp of the signature. - -- `signature` (`blob`): the actual signature. - -- `identity` (`principal`): the principal of the node producing the signature. - -Given a query (the `content` map from the request body) `Q`, a response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state`, the following predicate describes when the returned response `R` is correctly signed: - -``` -verify_response(Q, R, Cert) - = verify_cert(Cert) ∧ - ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ - (Cert.delegation ≠ NoDelegation ∧ SubnetId = Cert.delegation.subnet_id ∧ lookup*(["canister_ranges",SubnetId], Cert.delegation.certificate) = Ranges)) ∧ - effective_canister_id ∈ Ranges ∧ - ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. - lookup(["subnet",SubnetId,"node",NodeId,"public_key"], Cert) = Found PK ∧ - if R.status = "replied" then - verify_signature PK Sig ("\x0Bic-response" · hash_of_map({ - status: "replied", - reply: R.reply, - timestamp: T, - request_id: hash_of_map(Q)})) - else - verify_signature PK Sig ("\x0Bic-response" · hash_of_map({ - status: "rejected", - reject_code: R.reject_code, - reject_message: R.reject_message, - error_code: R.error_code, - timestamp: T, - request_id: hash_of_map(Q)})) -``` - -where `RootSubnetId` is the a priori known principal of the root subnet. Moreover, all timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation must be "recent enough". - -:::note - -This specification leaves it up to the client to define expiry times for the timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation. A reasonable expiry time for timestamps in `R.signatures` and the certificate `Cert` is 5 minutes (analogously to the maximum allowed ingress expiry enforced by the IC mainnet). Delegations require expiry times of at least a week since the IC mainnet refreshes the delegations only after replica upgrades which typically happen once a week. - -::: - -### Effective canister id {#http-effective-canister-id} - -The `` in the URL paths of requests is the *effective* destination of the request. -It must be contained in the canister ranges of a subnet, otherwise the corresponding HTTP request is rejected. - -- If the request is an update call to the Management Canister (`aaaaa-aa`), then: - - - If the call is to the `create_canister` or `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. - -- If the request is a query call to the Management Canister (`aaaaa-aa`), then: - - - If the call is to the `list_canisters` method, then any principal can be used as the effective canister id for this call. - - - 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. - -- If the request is an update or query call to a canister that is not the Management Canister (`aaaaa-aa`), then the effective canister id must be the `canister_id` in the request. - -:::note - -The expectation is that user-side agent code shields users and developers from the notion of effective canister id, in analogy to how the System API interface shields canister developers from worrying about routing. - -The Internet Computer blockchain mainnet does not support `provisional_create_canister_with_cycles` and thus all calls to this method are rejected independently of the effective canister id. - -In development instances of the Internet Computer Protocol (e.g. testnets), the effective canister id of a request submitted to a node must be a canister id from the canister ranges of the subnet to which the node belongs. - -::: - -### Authentication {#authentication} - -All requests coming in via the HTTPS interface need to be either *anonymous* or *authenticated* using a cryptographic signature. To that end, the following fields are present in the `content` map in all cases: - -- `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 call 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. The acceptance 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. Note that the `ingress_expiry` of a `read_state` request is independent of the `ingress_expiry` of an earlier `call` request, they do *not* need to be the same. - -- `sender` (`Principal`, required): The user who issued the request. - -The envelope, i.e. the overall request, has the following keys: - -- `content` (`record`): the actual request content - -- `sender_pubkey` (`blob`, optional): Public key used to authenticate this request. Since a user may in the future have more than one key, this field tells the IC which key is used. - -- `sender_delegation` (`array` of maps, optional): a chain of delegations, starting with the one signed by `sender_pubkey` and ending with the one delegating to the key relating to `sender_sig`. Every public key in the chain of delegations should appear exactly once: cycles (a public key delegates to another public key that already previously appeared in the chain) or self-signed delegations (a public key delegates to itself) are not allowed and such requests will be refused by the IC. - -- `sender_sig` (`blob`, optional): Signature to authenticate this request. - -The public key must authenticate the `sender` principal: - -- A public key can authenticate a principal if the latter is a self-authenticating id derived from that public key (see [Special forms of Principals](#id-classes)). - -- The fields `sender_pubkey`, `sender_sig`, and `sender_delegation` must be omitted if the `sender` field is the anonymous principal. The fields `sender_pubkey` and `sender_sig` must be set if the `sender` field is not the anonymous principal. - -The request id (see [Request ids](#request-id)) is calculated from the content record. This allows the signature to be based on the request id, and implies that signature and public key are not semantically relevant. - -The field `sender_pubkey` contains a public key supported by one of the schemes described in [Signatures](#signatures). - -Signing transactions can be delegated from one key to another one. If delegation is used, then the `sender_delegation` field contains an array of delegations, each of which is a map with the following fields: - -- `delegation` (`map`): Map with fields: - - - `pubkey` (`blob`): Public key as described in [Signatures](#signatures). - - - `expiration` (`nat`): Expiration of the delegation, in nanoseconds since 1970-01-01, analogously to the `ingress_expiry` field above. - - - `targets` (`array` of `CanisterId`, optional): If this field is set, the delegation only applies for requests sent to the canisters in the list. The list must contain no more than 1000 elements; otherwise, the request will not be accepted by the IC. - -- `signature` (`blob`): Signature on the 32-byte [representation-independent hash](#hash-of-map) of the map contained in the `delegation` field as described in [Signatures](#signatures), using the 27 bytes `\x1Aic-request-auth-delegation` as the domain separator. - - For the first delegation in the array, this signature is created with the key corresponding to the public key from the `sender_pubkey` field, all subsequent delegations are signed with the key corresponding to the public key contained in the preceding delegation. - -The `sender_sig` field is calculated by signing the concatenation of the 11 bytes `\x0Aic-request` (the domain separator) and the 32 byte [request id](#request-id) with the secret key that belongs to the key specified in the last delegation or, if no delegations are present, the public key specified in `sender_pubkey`. - -The delegation field, if present, must not contain more than 20 delegations. - -### Representation-independent hashing of structured data {#hash-of-map} - -Structured data, such as (recursive) maps, are authenticated by signing a representation-independent hash of the data. This hash is computed as follows (using SHA256 in the steps below): - -1. For each field that is present in the map (i.e. omitted optional fields are indeed omitted): - - - concatenate the hash of the field's name (in ascii-encoding, without terminal `\x00`) and the hash of the value (as specified below). - -2. Sort these concatenations from low to high. - -3. Concatenate the sorted elements, and hash the result. - -The resulting hash of length 256 bits (32 bytes) is the representation-independent hash. - -Field values are hashed as follows: - -- Binary blobs (`canister_id`, `arg`, `nonce`, `module`) are hashed as-is. - -- Strings (`request_type`, `method_name`) are hashed by hashing their binary encoding in UTF-8, without a terminal `\x00`. - -- Natural numbers (`compute_allocation`, `memory_allocation`, `ingress_expiry`) are hashed by hashing their binary encoding using the shortest form [Unsigned LEB128](https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128) encoding. For example, `0` should be encoded as a single zero byte `[0x00]` and `624485` should be encoded as byte sequence `[0xE5, 0x8E, 0x26]`. - -- Integers are hashed by hashing their encoding using the shortest form [Signed LEB128](https://en.wikipedia.org/wiki/LEB128#Signed_LEB128) encoding. For example, `0` should be encoded as a single zero byte `[0x00]` and `-123456` should be encoded as byte sequence `[0xC0, 0xBB, 0x78]`. - -- Arrays (`paths`) are hashed by hashing the concatenation of the hashes of the array elements. - -- Maps (`sender_delegation`) are hashed by recursively computing their representation-independent hash. - -:::tip - -Example calculation (where `H` denotes SHA-256 and `·` denotes blob concatenation) of a representation independent hash -for a map with a nested map in a field value: -``` -hash_of_map({ "reply": { "arg": "DIDL\x00\x00" } }) - = H(concat (sort [ H("reply") · hash_of_map({ "arg": "DIDL\x00\x00" }) ])) - = H(concat (sort [ H("reply") · H(concat (sort [ H("arg") · H("DIDL\x00\x00") ])) ])) -``` - -::: - -### Request ids {#request-id} - -When signing requests or querying the status of a request (see [Request status](#state-tree-request-status)) in the state tree, the user identifies the request using a *request id*, which is the [representation-independent hash](#hash-of-map) of the `content` map of the original request. A request id must have length of 32 bytes. - -:::note - -The request id is independent of the representation of the request (currently only CBOR, see [CBOR](#cbor)), and does not change if the specification adds further optional fields to a request type. - -::: - -:::note - -The recommended textual representation of a request id is a hexadecimal string with lower-case letters prefixed with '0x'. E.g., request id consisting of bytes `[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, 1C, 1D, 1E, 1F]` should be displayed as `0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f`. - -::: - -:::tip - -Example calculation (where `H` denotes SHA-256 and `·` denotes blob concatenation) in which we assume that the optional nonce is not provided and thus omitted: -``` -hash_of_map({ request_type: "call", sender: 0x04, ingress_expiry: 1685570400000000000, canister_id: 0x00000000000004D2, method_name: "hello", arg: "DIDL\x00\xFD*"}) - = H(concat (sort - [ H("request_type") · H("call") - , H("sender") · H("0x04") - , H("ingress_expiry") · H(1685570400000000000) - , H("canister_id") · H("\x00\x00\x00\x00\x00\x00\x04\xD2") - , H("method_name") · H("hello") - , H("arg") · H("DIDL\x00\xFD*") - ])) - = H(concat (sort - [ 769e6f87bdda39c859642b74ce9763cdd37cb1cd672733e8c54efaa33ab78af9 · 7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed - , 0a367b92cf0b037dfd89960ee832d56f7fc151681bb41e53690e776f5786998a · e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71 - , 26cec6b6a9248a96ab24305b61b9d27e203af14a580a5b1ff2f67575cab4a868 · db8e57abc8cda1525d45fdd2637af091bc1f28b35819a40df71517d1501f2c76 - , 0a3eb2ba16702a387e6321066dd952db7a31f9b5cc92981e0a92dd56802d3df9 · 4d8c47c3c1c837964011441882d745f7e92d10a40cef0520447c63029eafe396 - , 293536232cf9231c86002f4ee293176a0179c002daa9fc24be9bb51acdd642b6 · 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 - , b25f03dedd69be07f356a06fe35c1b0ddc0de77dcd9066c4be0c6bbde14b23ff · 6c0b2ae49718f6995c02ac5700c9c789d7b7862a0d53e6d40a73f1fcd2f70189 - ])) - = H(concat - [ 0a367b92cf0b037dfd89960ee832d56f7fc151681bb41e53690e776f5786998a · e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71 - , 0a3eb2ba16702a387e6321066dd952db7a31f9b5cc92981e0a92dd56802d3df9 · 4d8c47c3c1c837964011441882d745f7e92d10a40cef0520447c63029eafe396 - , 26cec6b6a9248a96ab24305b61b9d27e203af14a580a5b1ff2f67575cab4a868 · db8e57abc8cda1525d45fdd2637af091bc1f28b35819a40df71517d1501f2c76 - , 293536232cf9231c86002f4ee293176a0179c002daa9fc24be9bb51acdd642b6 · 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 - , 769e6f87bdda39c859642b74ce9763cdd37cb1cd672733e8c54efaa33ab78af9 · 7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed - , b25f03dedd69be07f356a06fe35c1b0ddc0de77dcd9066c4be0c6bbde14b23ff · 6c0b2ae49718f6995c02ac5700c9c789d7b7862a0d53e6d40a73f1fcd2f70189 - ]) - = 1d1091364d6bb8a6c16b203ee75467d59ead468f523eb058880ae8ec80e2b101 -``` - -::: - -### Reject codes {#reject-codes} - -An API request or inter-canister call that is pending in the IC will eventually result in either a *reply* (indicating success, and carrying data) or a *reject* (indicating an error of some sorts). A reject contains a *reject code* that classifies the error and a hopefully helpful *reject message* string. - -Reject codes are member of the following enumeration: - -- `SYS_FATAL` (1): Fatal system error, retry unlikely to be useful. - -- `SYS_TRANSIENT` (2): Transient system error, retry might be possible. - -- `DESTINATION_INVALID` (3): Invalid destination (e.g. canister/account does not exist) - -- `CANISTER_REJECT` (4): Explicit reject by the canister. - -- `CANISTER_ERROR` (5): Canister error (e.g., trap, no response) - -- `SYS_UNKNOWN` (6): Response unknown; system stopped waiting for it (e.g., timed out, or system under high load). This code is only applicable to inter-canister calls that used `ic0.call_with_best_effort_response`. - -The symbolic names of this enumeration are used throughout this specification, but on all interfaces (HTTPS API, System API), they are represented as positive numbers as given in the list above. - -The error message is guaranteed to be a string, i.e. not arbitrary binary data. - -When canisters explicitly reject a message (see [Public methods](#system-api-requests)), they can specify the reject message, but *not* the reject code; it is always `CANISTER_REJECT`. In this sense, the reject code is trustworthy: the reject code is always fixed by the protocol, i.e., the canister cannot freely specify the reject code. - -### Error codes {#error-codes} - -Implementations of the API can provide additional details for rejected messages in the form of a textual label identifying the error condition. API clients can use these labels to handle errors programmatically or suggest recovery paths to the user. The specification reserves error codes matching the regular expression `IC[0-9]+` (e.g., `IC502`) for the DFINITY implementation of the API. - -### Status endpoint {#api-status} - -Additionally, the Internet Computer provides an API endpoint to obtain various status fields at - - /api/v2/status - -For this endpoint, the user performs a GET request, and receives a CBOR (see [CBOR](#cbor)) value with the following fields. The IC may include additional implementation-specific fields. - -- `root_key` (blob, optional): The public key (a DER-encoded BLS key) of the root key of this instance of the Internet Computer Protocol. This *must* be present in short-lived development instances, to allow the agent to fetch the public key. For the Internet Computer, agents must have an independent trustworthy source for this data (e.g., the system API `ic0.root_key_size` and `ic0.root_key_copy`), and must not be tempted to fetch it from this insecure location. - -See [CBOR encoding of requests and responses](#api-cbor) for details on the precise CBOR encoding of this object. - -:::note - -Future additions may include local time, geographic location, and other useful implementation-specific information such as blockheight. This data may possibly be signed by the node. - -::: - -### CBOR encoding of requests and responses {#api-cbor} - -Requests and responses are specified here as records with named fields and using suggestive human readable syntax. The actual format in the body of the HTTP request or response, however, is CBOR (see [CBOR](#cbor)). - -Concretely, it consists of a data item with major type 6 ("Semantic tag") and tag value `55799`, followed by a record. - -Requests consist of an envelope record with keys `sender_sig` (a blob), `sender_pubkey` (a blob) and `content` (a record). The first two are metadata that are used for request authentication, while the last one is the actual content of the request. - -The following encodings are used: - -- Strings: Major type 3 ("Text string"). - -- Blobs: Major type 2 ("Byte string"). - -- Nats: Major type 0 ("Unsigned integer") if small enough to fit that type, else the [Bignum](https://www.rfc-editor.org/rfc/rfc8949#name-bignums) format is used. - -- Records: Major type 5 ("Map of pairs of data items"), followed by the fields, where keys are encoded with major type 3 ("Text string"). - -- Arrays: Major type 4 ("Array of data items"). - -As advised by [section "Creating CBOR-Based Protocols"](https://www.rfc-editor.org/rfc/rfc8949#name-creating-cbor-based-protoco) of the CBOR spec, we clarify that: - -- Floating-point numbers may not be used to encode integers. - -- Duplicate keys are prohibited in CBOR maps. - -:::tip - -A typical request would be (written in [CBOR diagnostic notation](https://www.rfc-editor.org/rfc/rfc8949#name-diagnostic-notation), which can be checked and converted on [cbor.me](https://cbor.me/)): -``` -55799({ - "content": { - "request_type": "call", - "canister_id": h'ABCD01', - "method_name": "say_hello", - "arg": h'0061736d01000000' - }, - "sender_sig": h'DEADBEEF', - "sender_pubkey": h'b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde' -}) -``` - -::: - -### CDDL description of requests and responses {#api-cddl} - -This section summarizes the format of the CBOR data passed to and from the entry points described above. You can also [download the file](_attachments/requests.cddl) and see [CDDL](#cddl) for more information. - -### Ordering guarantees - -The order in which the various messages between canisters are delivered and executed is not fully specified. The guarantee provided by the IC is that if a canister sends two messages to a canister and they both start being executed by the receiving canister, then they do so in the order in which the messages were sent. - -More precisely: - -- Messages between any *two* canisters, if delivered to the canister, start executing in order. Note that message delivery can fail for arbitrary reasons (e.g., high system load). - -- If a WebAssembly function, within a single invocation, makes multiple calls to the same canister, they are queued in the order of invocations to `ic0.call_perform`. - -- Responses (including replies with `ic0.msg_reply`, explicit rejects with `ic0.msg_reject` and system-generated error responses) do *not* have any ordering guarantee relative to each other or to method calls. - -- There is no particular order guarantee for ingress messages submitted via the HTTPS interface. - -### Synchronicity across nodes - -This document describes the Internet Computer as having a single global state that can be modified and queried. In reality, it consists of many nodes, which may not be perfectly in sync. - -As long as you talk to one (honest) node only, the observed behavior is nicely sequential. If you issue an update (i.e. state-mutating) call to a canister (e.g. bump a counter), and node A indicates that the call has been executed, and you then issue a query call to node A, then A's response is guaranteed to include the effect of the update call (and you will receive the updated counter value). - -If you then (quickly) issue a read request to node B, it may be that B responds to your read query based on the old state of the canister (and you might receive the old counter value). - -A related problem is that query calls are not certified, and nodes may be dishonest in their response. In that case, the user might want to get more assurance by querying multiple nodes and comparing the result. However, it is (currently) not possible to query a *specific* state. - -:::note - -Applications can work around these problems. For the first problem, the query result could be such that the user can tell if the update has been received or not. For the second problem, even if using [certified data](#system-api-certified-data) is not possible, if replies are monotonic in some sense the user can get assurance in their intersection (e.g. if the query returns a list of events that grows over time, then even if different nodes return different lists, the user can get assurance in those events that are reported by many nodes). - -::: - -## Canister module format {#canister-module-format} - -A canister module is a [WebAssembly module](https://webassembly.github.io/spec/core/index.html) that is either in binary format (typically `.wasm`) or gzip-compressed (typically `.wasm.gz`). If the module starts with byte sequence `[0x1f, 0x8b, 0x08]`, then the system decompresses the contents as a gzip stream according to [RFC-1952](https://datatracker.ietf.org/doc/html/rfc1952.html) and then parses the output as a WebAssembly binary. - -## Canister interface (System API) {#system-api} - -The System API is the interface between the running canister and the Internet Computer. It allows the WebAssembly module of a canister to expose functionality to the users (method entry points) and the IC (e.g. initialization), and exposes functionality of the IC to the canister (e.g. calling other canisters). Because WebAssembly is rather low-level, it also explains how to express higher level concepts (e.g. binary blobs). - -We want to leverage advanced WebAssembly features, such as WebAssembly host references. But as they are not yet supported by all tools involved, this section describes an initial System API that does not rely on host references. In section [Outlook: Using Host References](#host-references), we outline some of the proposed uses of WebAssembly host references. - -### WebAssembly module requirements {#system-api-module} - -In order for a WebAssembly module to be usable as the code for the canister, it needs to conform to the following requirements: - -- It may declare (import or export) at most one memory. - -- It may only import a function if it is listed in [Overview of imports](#system-api-imports). - In particular, all imported functions belong to the `ic0` module (denoted by the prefix `ic0.`). - The value of `I ∈ {i32, i64}` specifying whether the imported functions have 32-bit or 64-bit pointers - is derived from the bit-width of the declared memory defaulting to `I = i32` if the canister declares no memory. - -- It may have a `(start)` function. - -- If it exports a function called `canister_init`, the function must have type `() -> ()`. - -- If it exports a function called `canister_inspect_message`, the function must have type `() -> ()`. - -- If it exports a function called `canister_pre_upgrade`, the function must have type `() -> ()`. - -- If it exports a function called `canister_post_upgrade`, the function must have type `() -> ()`. - -- If it exports a function called `canister_heartbeat`, the function must have type `() -> ()`. - -- If it exports a function called `canister_on_low_wasm_memory`, the function must have type `() -> ()`. - -- If it exports a function called `canister_global_timer`, the function must have type `() -> ()`. - -- If it exports any functions called `canister_update `, `canister_query `, or `canister_composite_query ` for some `name`, the functions must have type `() -> ()`. - -- It may not export more than one function called `canister_update `, `canister_query `, or `canister_composite_query ` with the same `name`. - -- It may not export other methods the names of which start with the prefix `canister_` besides the methods allowed above. - -- It may not have both `icp:public ` and `icp:private ` with the same `name` as the custom section name. - -- It may not have other custom sections the names of which start with the prefix `icp:` besides the `icp:public ` and `icp:private `. - -- The IC may reject WebAssembly modules that - - - declare more than 50,000 functions, or - - - declare more than 1,000 globals, or - - - declare more than 16 exported custom sections (the custom section names with prefix `icp:`), or - - - the number of all exported functions called `canister_update `, `canister_query `, or `canister_composite_query ` exceeds 1,000, or - - - the sum of `` lengths in all exported functions called `canister_update `, `canister_query `, or `canister_composite_query ` exceeds 20,000, or - - - the total size of the custom sections (the sum of `` lengths in their names `icp:public ` and `icp:private ` plus the sum of their content lengths) exceeds 1MiB. - -### Interpretation of numbers - -WebAssembly number types (`i32`, `i64`) do not indicate if the numbers are to be interpreted as signed or unsigned. Unless noted otherwise, whenever the System API interprets them as numbers (e.g. memory pointers, buffer offsets, array sizes), they are to be interpreted as unsigned. - -### Entry points {#entry-points} - -The canister provides entry points which are invoked by the IC under various circumstances: - -- The canister may export a function with name `canister_init` and type `() -> ()`. - -- The canister may export a function with name `canister_pre_upgrade` and type `() -> ()`. - -- The canister may export a function with name `canister_post_upgrade` and type `() -> ()`. - -- The canister may export functions with name `canister_inspect_message` with type `() -> ()`. - -- The canister may export a function with name `canister_heartbeat` with type `() -> ()`. - -- The canister may export a function with name `canister_global_timer` with type `() -> ()`. - -- The canister may export functions with name `canister_update ` and type `() -> ()`. - -- The canister may export functions with name `canister_query ` and type `() -> ()`. - -- The canister may export functions with name `canister_composite_query ` and type `() -> ()`. - -- The canister may export a function with the name `canister_on_low_wasm_memory` and type `() -> ()`. - -- The canister table may contain functions of type `(env : I) -> ()` which may be used as callbacks for inter-canister calls and composite query methods. - The value of `I ∈ {i32, i64}` specifying whether the imported functions have 32-bit or 64-bit pointers - is derived from the bit-width of the declared memory defaulting to `I = i32` if the canister declares no memory. - -If the execution of any of these entry points traps for any reason, then all changes to the WebAssembly state, as well as the effect of any externally visible system call (like `ic0.msg_reply`, `ic0.msg_reject`, `ic0.call_perform`), are discarded. For upgrades, this transactional behavior applies to the `canister_pre_upgrade`/`canister_post_upgrade` sequence as a whole. - -#### Canister initialization {#system-api-init} - -If `canister_init` is present, then this is the first exported WebAssembly function invoked by the IC. The argument that was passed along with the canister initialization call (see [IC method](#ic-install_code)) is available to the canister via `ic0.msg_arg_data_size/copy`. - -The IC assumes the canister to be fully instantiated if the `canister_init` method entry point returns. If the `canister_init` method entry point traps, then canister installation has failed, and the canister is reverted to its previous state (i.e. empty with `install`, or whatever it was for a `reinstall`). - -#### Canister upgrades {#system-api-upgrades} - -When a canister is upgraded to a new WebAssembly module, the IC: - -1. Invokes `canister_pre_upgrade` (if exported by the current canister code and `skip_pre_upgrade` is not `opt true`) on the old instance, to give the canister a chance to clean up (e.g. move data to [stable memory](#system-api-stable-memory)). - -2. Instantiates the new module, including the execution of `(start)`, with a fresh WebAssembly state. - -3. Invokes `canister_post_upgrade` (if present) on the new instance, passing the `arg` provided in the `install_code` call ([IC method](#ic-install_code)). - -The stable memory is preserved throughout the process; the WebAssembly memory is discarded unless `wasm_memory_persistence` is `opt keep`; any other WebAssembly state is discarded. - -During these steps, no other entry point of the old or new canister is invoked. The `canister_init` function of the new canister is *not* invoked. - -These steps are atomic: If `canister_pre_upgrade` or `canister_post_upgrade` trap, the upgrade has failed, and the canister is reverted to the previous state. Otherwise, the upgrade has succeeded, and the old instance is discarded. - -:::note -The `skip_pre_upgrade` flag can be enabled to skip the execution of the `canister_pre_upgrade` method on the old canister instance. -The main purpose of this mode is recovery from cases when the `canister_pre_upgrade` hook traps unconditionally preventing the normal upgrade path. - -Skipping the pre-upgrade can lead to data loss. -Use it only as the last resort and only if the stable memory already contains the entire canister state. -::: - -#### Public methods {#system-api-requests} - -To define a public method of name `name`, a WebAssembly module exports a function with name `canister_update `, `canister_query `, or `canister_composite_query ` and type `() -> ()`. We call this the *method entry point*. The name of the exported function distinguishes update, query, and composite query methods. - -:::note - -The space in `canister_update `, `canister_query `, and `canister_composite_query `, resp., is intentional. There is exactly one space between `canister_update/canister_query/canister_composite_query` and the ``. - -::: - -The argument of the call (e.g. the content of the `arg` field in the [API request to call a canister method](#http-call)) is copied into the canister on demand using the System API functions shown below. - -Eventually, a method will want to send a response, using `ic0.reply` or `ic0.reject` - -#### Heartbeat - -For periodic or time-based execution, the WebAssembly module can export a function with name `canister_heartbeat`. The heartbeats scheduling algorithm is implementation-defined. - -`canister_heartbeat` is triggered by the IC, and therefore has no arguments and cannot reply or reject. Still, the function `canister_heartbeat` can initiate new calls. - -:::note - -While an implementation will likely try to keep the interval between `canister_heartbeat` invocations to within a few seconds, this is not formally part of this specification. - -::: - -#### Global timer {#global-timer} - -For time-based execution, the WebAssembly module can export a function with name `canister_global_timer`. This function is called if the canister has set its global timer (using the System API function `ic0.global_timer_set`) and the current time (as returned by the System API function `ic0.time`) has passed the value of the global timer. - -Once the function `canister_global_timer` is scheduled, the canister's global timer is deactivated. The global timer is also deactivated upon changes to the canister's Wasm module (calling `install_code`, `install_chunked_code`, `uninstall_code` methods of the management canister or if the canister runs out of cycles). In particular, the function `canister_global_timer` won't be scheduled again unless the canister sets the global timer again (using the System API function `ic0.global_timer_set`). The global timer scheduling algorithm is implementation-defined. - -`canister_global_timer` is triggered by the IC, and therefore has no arguments and cannot reply or reject. Still, the function `canister_global_timer` can initiate new calls. - -:::note - -While an implementation will likely try to keep the interval between the value of the global timer and the time-stamp of the `canister_global_timer` invocation within a few seconds, this is not formally part of this specification. - -::: - -#### On Low Wasm Memory {#on-low-wasm-memory} - -A canister can export a function with the name `canister_on_low_wasm_memory`, which is scheduled whenever the canister's remaining wasm memory size in bytes falls from at least a threshold `t` to strictly less than `t`. -The threshold `t` can be defined in the field `wasm_memory_threshold` in the [canister's settings](#ic-update_settings) and by default it is set to 0. - -:::note - -While the above function is scheduled immediately once the condition above is triggered, it may not necessarily be executed immediately if the canister does not have enough cycles. -If the canister gets frozen immediately after the function is scheduled for execution, the function will run once the canister's unfrozen _if_ the canister's remaining wasm memory size in bytes remains strictly less than the threshold `t`. -::: - -#### Callbacks - -Callbacks are addressed by their table index (as a proxy for a Wasm `funcref`). - -In the reply callback of a [inter-canister method call](#system-api-call), the argument refers to the response to that call. In reject callbacks, no argument is available. - -### Replicated and Non-Replicated execution mode - -Canister methods can be executed either in *replicated* mode where the method runs on all subnet nodes and the results go through consensus or in *non-replicated* mode where the method runs on a single node and the result does not go through consensus. The trade-off between replicated and non-replicated mode is therefore one between the result's latency and trustworthiness. - -The following table captures the modes that different canister methods can be executed in. - -| Canister method | Replicated Mode | Non-Replicated Mode | -| --------------------------- | --------------- | ------------------- | -| canister_update | Yes | No | -| canister_query | Yes | Yes | -| canister_composite_query | No | Yes | -| canister_inspect_message | No | Yes | -| canister_init | Yes | No | -| canister_pre_upgrade | Yes | No | -| canister_post_upgrade | Yes | No | -| canister_heartbeat | Yes | No | -| canister_global_timer | Yes | No | -| canister_on_low_wasm_memory | Yes | No | - -### Overview of imports {#system-api-imports} - -:::note - -The 32-bit stable memory System API (`ic0.stable_size`, `ic0.stable_grow`, `ic0.stable_write`, and `ic0.stable_read`) is DEPRECATED. Canister developers are advised to use the 64-bit stable memory System API instead. - -::: - -The following sections describe various System API functions, also referred to as system calls, which we summarize here. - -All the following functions belong to the `ic0` module (denoted by the prefix `ic0.`). - -In the following, the value of `I ∈ {i32, i64}` specifies whether the imported functions have 32-bit or 64-bit pointers. -Given a canister module, the value of `I ∈ {i32, i64}` is derived from the bit-width of the declared memory -defaulting to `I = i32` if the canister declares no memory. - -``` - ic0.msg_arg_data_size : () -> I; // I U RQ NRQ TQ CQ Ry CRy F - ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) -> (); // I U RQ NRQ TQ CQ Ry CRy F - ic0.msg_caller_size : () -> I; // * - ic0.msg_caller_copy : (dst : I, offset : I, size : I) -> (); // * - ic0.msg_caller_info_data_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F - ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F - ic0.msg_caller_info_signer_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F - ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F - ic0.msg_reject_code : () -> i32; // Ry Rt CRy CRt C - ic0.msg_reject_msg_size : () -> I ; // Rt CRt - ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) -> (); // Rt CRt - - ic0.msg_deadline : () -> i64; // U Q CQ Ry Rt CRy CRt - - ic0.msg_reply_data_append : (src : I, size : I) -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt - ic0.msg_reply : () -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt - ic0.msg_reject : (src : I, size : I) -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt - - ic0.msg_cycles_available128 : (dst : I) -> (); // U RQ Rt Ry - ic0.msg_cycles_refunded128 : (dst : I) -> (); // Rt Ry - ic0.msg_cycles_accept128 : (max_amount_high : i64, max_amount_low: i64, dst : I) - -> (); // U RQ Rt Ry - - ic0.cycles_burn128 : (amount_high : i64, amount_low : i64, dst : I) - -> (); // I G U RQ Ry Rt C T - - ic0.canister_self_size : () -> I; // * - ic0.canister_self_copy : (dst : I, offset : I, size : I) -> (); // * - ic0.canister_cycle_balance128 : (dst : I) -> (); // * - ic0.canister_liquid_cycle_balance128 : (dst : I) -> (); // * - ic0.canister_status : () -> i32; // * - ic0.canister_version : () -> i64; // * - - ic0.subnet_self_size : () -> I; // * - ic0.subnet_self_copy : (dst : I, offset : I, size : I) -> (); // * - - ic0.msg_method_name_size : () -> I; // F - ic0.msg_method_name_copy : (dst : I, offset : I, size : I) -> (); // F - ic0.accept_message : () -> (); // F - - ic0.call_new : - ( callee_src : I, - callee_size : I, - name_src : I, - name_size : I, - reply_fun : I, - reply_env : I, - reject_fun : I, - reject_env : I - ) -> (); // U CQ Ry Rt CRy CRt T - ic0.call_on_cleanup : (fun : I, env : I) -> (); // U CQ Ry Rt CRy CRt T - ic0.call_data_append : (src : I, size : I) -> (); // U CQ Ry Rt CRy CRt T - ic0.call_with_best_effort_response : (timeout_seconds : i32) -> (); // U CQ Ry Rt CRy CRt T - ic0.call_cycles_add128 : (amount_high : i64, amount_low: i64) -> (); // U Ry Rt T - ic0.call_perform : () -> ( err_code : i32 ); // U CQ Ry Rt CRy CRt T - - ic0.stable64_size : () -> (page_count : i64); // * s - ic0.stable64_grow : (new_pages : i64) -> (old_page_count : i64); // * s - ic0.stable64_write : (offset : i64, src : i64, size : i64) -> (); // * s - ic0.stable64_read : (dst : i64, offset : i64, size : i64) -> (); // * s - - ic0.root_key_size : () -> I; // I G U RQ Ry Rt C T - ic0.root_key_copy : (dst : I, offset : I, size : I) -> (); // I G U RQ Ry Rt C T - ic0.certified_data_set : (src : I, size : I) -> (); // I G U Ry Rt T - ic0.data_certificate_present : () -> i32; // * - ic0.data_certificate_size : () -> I; // NRQ CQ - ic0.data_certificate_copy : (dst : I, offset : I, size : I) -> (); // NRQ CQ - - ic0.time : () -> (timestamp : i64); // * - ic0.global_timer_set : (timestamp : i64) -> i64; // I G U Ry Rt C T - ic0.performance_counter : (counter_type : i32) -> (counter : i64); // * s - ic0.is_controller : (src : I, size : I) -> ( result : i32); // * s - ic0.in_replicated_execution : () -> (result : i32); // * s - - ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> (); // * s - ic0.cost_create_canister : (dst : I) -> (); // * s - ic0.cost_http_request : (request_size : i64, max_res_bytes : i64, dst : I) -> (); // * s - ic0.cost_sign_with_ecdsa : (src : I, size : I, ecdsa_curve: i32, dst : I) -> i32; // * s - ic0.cost_sign_with_schnorr : (src : I, size : I, algorithm: i32, dst : I) -> i32; // * s - ic0.cost_vetkd_derive_key : (src : I, size : I, vetkd_curve: i32, dst : I) -> i32; // * s - - ic0.env_var_count : () -> I; // * - - ic0.env_var_name_size : (index: I) -> I; // * - ic0.env_var_name_copy : (index: I, dst: I, offset: I, size: I) -> (); // * - ic0.env_var_name_exists : (name_src: I, name_size: I) -> i32; // * - - ic0.env_var_value_size : (name_src: I, name_size: I) -> I; // * - ic0.env_var_value_copy : (name_src: I, name_size: I, dst: I, offset: I, size: I) -> (); // * - - ic0.debug_print : (src : I, size : I) -> (); // * s - ic0.trap : (src : I, size : I) -> (); // * s -``` - -The following System API functions are only available if `I = i32`, i.e., if the bit-width of the declared memory is 32 -or if the canister declares no memory. - -``` - ic0.msg_cycles_available : () -> i64; // U RQ Rt Ry - ic0.msg_cycles_refunded : () -> i64; // Rt Ry - ic0.msg_cycles_accept : (max_amount : i64) -> (amount : i64); // U RQ Rt Ry - ic0.canister_cycle_balance : () -> i64; // * - ic0.call_cycles_add : (amount : i64) -> (); // U Ry Rt T - ic0.stable_size : () -> (page_count : i32); // * s - ic0.stable_grow : (new_pages : i32) -> (old_page_count : i32); // * s - ic0.stable_write : (offset : i32, src : i32, size : i32) -> (); // * s - ic0.stable_read : (dst : i32, offset : i32, size : i32) -> (); // * s -``` - -The comment after each function lists from where these functions may be invoked: - -- `I`: from `canister_init` or `canister_post_upgrade` - -- `G`: from `canister_pre_upgrade` - -- `U`: from `canister_update …` - -- `RQ`: from `canister_query …` in replicated mode - -- `NRQ`: from `canister_query …` in non-replicated mode - -- `TQ`: from `canister_query …` in canister http outcall transform - -- `CQ`: from `canister_composite_query …` - -- `Ry`: from a reply callback - -- `Rt`: from a reject callback - -- `CRy`: from a reply callback in composite query - -- `CRt`: from a reject callback in composite query - -- `C`: from a cleanup callback - -- `CC`: from a cleanup callback in composite query - -- `s`: the `(start)` module initialization function - -- `F`: from `canister_inspect_message` - -- `T`: from *system task* (`canister_heartbeat` or `canister_global_timer` or `canister_on_low_wasm_memory`) - -- `*` = `I G U RQ NRQ TQ CQ Ry Rt CRy CRt C CC F T` (NB: Not `(start)`) - -If the canister invokes a system call from somewhere else, it will trap. - -Since Wasm doesn't have a 128-bit number type, calls requiring 128-bit arguments (e.g., the 128-bit versions of cycle operations) encode such arguments as a pair of 64-bit numbers containing the high and low bits. - -### Blob-typed arguments and results - -WebAssembly functions parameter and result types can only be primitive number types. To model functions that accept or return blobs or text values, the following idiom is used: - -To provide access to a string or blob `foo`, the System API provides two functions: -``` -ic0.foo_size : () -> I; I ∈ {i32, i64} -ic0.foo_copy : (dst : I, offset : I, size : I) -> (); I ∈ {i32, i64} -``` - -The `*_size` function indicates the size, in bytes, of `foo`. The `*_copy` function copies `size` bytes from `foo[offset..offset+size]` to `memory[dst..dst+size]`. This traps if `offset+size` is greater than the size of `foo`, or if `dst+size` exceeds the size of the Wasm memory. - -Dually, a System API function that conceptually takes a blob or string as a parameter `foo` has two parameters: - -``` -ic0.set_foo : (src : I, size : I) -> …; I ∈ {i32, i64} -``` - -which copies, at the time of function invocation, the data referred to by `src`/`size` out of the canister. Unless otherwise noted, this traps if `src+size` exceeds the size of the WebAssembly memory. - -### Method arguments - -The canister can access an argument. For `canister_init`, `canister_post_upgrade` and method entry points, the argument is the argument of the call; in a reply callback, it refers to the received reply. So the lifetime of the argument data is a single WebAssembly function execution, not the whole method call tree. - -- `ic0.msg_arg_data_size : () → I` and `ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - The message argument data. - -- `ic0.msg_caller_size : () → I` and `ic0.msg_caller_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - The identity of the caller, which may be a canister id or a user id. During canister installation or upgrade, this is the id of the user or canister requesting the installation or upgrade. During a system task (heartbeat or global timer), this is the id of the management canister. - -- `ic0.msg_caller_info_data_size : () → I`, `ic0.msg_caller_info_signer_size : () → I` and `ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) → ()`; and `ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - Auxiliary information about the caller as provided by the canister with which the caller's identity is associated (i.e., the public key of the canister signature is equal to the public key of the caller's identity). - These functions can only return non-empty values if the caller is a self-authenticating principal authenticated by canister signatures. In particular, they always return empty values when the caller is another canister. - - The `caller_info_data` may include information such as identity attributes of the caller. - The `_signer_` functions return the canister ID of the canister providing the signature, and the `_data_` functions return the data provided by the canister. - This auxiliary information can only be set if the caller principal is derived from the public key corresponding to a canister signature, and it is guaranteed to be properly signed by the issuing canister. - - These functions trap in `canister_init`, `canister_post_upgrade`, `canister_pre_upgrade`, canister http outcall transform, the `(start)` module initialization function, and system tasks (`canister_heartbeat` or `canister_global_timer` or `canister_on_low_wasm_memory`). - -- `ic0.msg_reject_code : () → i32` - - Returns the reject code, if the current function is invoked as a reject callback or as a cleanup callback of a reject callback. - - It returns the special "no error" code `0` if the callback is a reply callback or a cleanup callback of a reply callback; this allows canisters to use a single entry point for both the reply and reject callback, if they choose to do so. - -- `ic0.msg_reject_msg_size : () → I` and `ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - The reject message. Traps if there is no reject message (i.e. if `reject_code` is `0`). - -- `ic0.msg_deadline : () -> i64` - - The deadline, in nanoseconds since 1970-01-01, after which the caller might stop waiting for a response. - - For update methods and their callbacks, if the method was called via a bounded-wait inter-canister call the deadline is computed based on the time the call was made, and the `timeout_seconds` parameter provided by the caller. For other calls (including ingress messages and all calls to query and composite query methods, including calls in replicated mode) a deadline of 0 is returned. - -### Responding {#responding} - -Eventually, the canister will want to respond to the original call, either by replying (indicating success) or rejecting (signalling an error): - -- `ic0.msg_reply_data_append : (src : I, size : I) → ()`; `I ∈ {i32, i64}` - - Appends data it to the (initially empty) data reply. Traps if the total appended data exceeds the [maximum response size](./cycles-costs.md). - - This traps if the current call already has been or does not need to be responded to. - - Any data assembled, but not replied using `ic0.msg_reply`, gets discarded at the end of the current message execution. In particular, the reply buffer gets reset when the canister yields control without calling `ic0.msg_reply`. - -:::note - -This can be invoked multiple times within the same message execution to build up the argument with data from various places on the Wasm heap. This way, the canister does not have to first copy all the pieces from various places into one location. - -::: - -- `ic0.msg_reply : () → ()` - - Replies to the sender with the data assembled using `ic0.msg_reply_data_append`. - - This function can be called at most once (a second call will trap), and must be called exactly once to indicate success. - - See [Cycles](#system-api-cycles) for how this interacts with cycles available on this call. - -- `ic0.msg_reject : (src : I, size : I) → ()`; `I ∈ {i32, i64}` - - Rejects the call. The data referred to by `src`/`size` is used for the diagnostic message. - - This system call traps if `src+size` exceeds the size of the WebAssembly memory, or if the current call already has been or does not need to be responded to, or if the data referred to by `src`/`size` is not valid UTF8. - - The other end will receive this reject with reject code `CANISTER_REJECT`, see [Reject codes](#reject-codes). - - Possible reply data assembled using `ic0.msg_reply_data_append` is discarded. - - See [Cycles](#system-api-cycles) for how this interacts with cycles available on this call. - -### Ingress message inspection {#system-api-inspect-message} - -A canister can inspect ingress messages before executing them. When the IC receives an update call from a user, the IC will use the canister method `canister_inspect_message` to determine whether the message shall be accepted. If the canister is empty (i.e. does not have a Wasm module), then the ingress message will be rejected. If the canister is not empty and does not implement `canister_inspect_message`, then the ingress message will be accepted. - -In `canister_inspect_message`, the canister can determine the name of the method called by the message using `ic0.msg_method_name_size : () → I` and `ic0.msg_method_name_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}`. - -In `canister_inspect_message`, the canister can accept the message by invoking `ic0.accept_message : () → ()`. This function traps if invoked twice. If the canister traps in `canister_inspect_message` or does not call `ic0.accept_message`, then the access is denied. - -:::note - -The `canister_inspect_message` is executed by a single node and thus its outcome depends on the state of this node. -In particular, the `canister_inspect_message` might be executed on a state that does not reflect the changes -made by a previously successfully completed update call if the `canister_inspect_message` is executed by a node -that is not up-to-date in terms of its state. - -::: - -:::note - -The `canister_inspect_message` is *not* invoked for query calls, inter-canister calls or calls to the management canister. - -::: - -### Self-identification {#system-api-canister-self} - -A canister can learn about its own identity: - -- `ic0.canister_self_size : () → I` and `ic0.canister_self_copy: (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - These functions allow the canister to query its own canister id (as a blob). - -A canister can learn about the subnet it is running on: - -- `ic0.subnet_self_size : () → I` and `ic0.subnet_self_copy: (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - These functions allow the canister to query the subnet id (as a blob) of the subnet on which the canister is running. - -### Canister status {#system-api-canister-status} - -This function allows a canister to find out if it is running, stopping or stopped (see [IC method](#ic-canister_status) and [IC method](#ic-stop_canister) for context). - -- `ic0.canister_status : () → i32` - - returns the current status of the canister: - - Status `1` indicates running, `2` indicates stopping, and `3` indicates stopped. - - Observing the canister status is particularly useful during `canister_post_upgrade`. Confirming that the status is 3 (stopped) helps prevent accidentally upgrading a canister that has not fully stopped. - -### 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 ID, canister's code, settings, running status (Running, Stopping, Stopped), cycles balance, and memory (WASM and stable memory), i.e., upon every successful canister renaming, 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, replicated query methods, response callbacks, heartbeats, and global timers. The system can also arbitrarily increment the canister version at any time. - -- `ic0.canister_version : () → i64` - - returns the current canister version. - -During the canister upgrade process, `canister_pre_upgrade` sees the old counter value, and `canister_post_upgrade` sees the new counter value. - -### Inter-canister method calls {#system-api-call} - -When handling an update call (or a callback), a canister can do further calls to another canister. Calls are assembled in a builder-like fashion, starting with `ic0.call_new`, adding more attributes using the `ic0.call_*` functions, and eventually performing the call with `ic0.call_perform`. - -- `ic0.call_new : - ( callee_src : I, - callee_size : I, - name_src : I, - name_size : I, - reply_fun : I, - reply_env : I, - reject_fun : I, - reject_env : I, - ) → ()`; `I ∈ {i32, i64}` - -Begins assembling a call to the canister specified by `callee_src/_size` at method `name_src/_size`. - -The IC records two mandatory callback functions, represented by a table entry index `*_fun` and some additional value `*_env`. When the response comes back, the table is read at the corresponding index, expected to be a function of type `(env : I) -> ()`, and passed the corresponding `*_env` value. - -The reply callback is executed upon successful completion of the method call, which can query the reply using `ic0.msg_arg_data_*`. - -The reject callback is executed if the method call fails asynchronously or the other canister explicitly rejects the call. The reject code and message can be queried using `ic0.msg_reject_code` and `ic0.msg_reject_msg_*`. - -Subsequent calls to the following functions set further attributes of that call, until the call is concluded (with `ic0.call_perform`) or discarded (by returning without calling `ic0.call_perform` or by starting a new call with `ic0.call_new`.) - -- `ic0.call_on_cleanup : (fun : I, env : I) → ()`; `I ∈ {i32, i64}` - -If a cleanup callback (of type `(env : I) -> ()`) is specified for this call, it is executed if and only if the `reply` or the `reject` callback was executed and trapped (for any reason). - -During the execution of the `cleanup` function, only a subset of the System API is available. The cleanup function is expected to run swiftly (within a fixed, yet to be specified cycle limit) and serves to free resources associated with the callback. - -If this traps (e.g. runs out of cycles), the state changes from the `cleanup` function are discarded, as usual, and no further actions are taken related to that call. Canisters likely want to avoid this from happening. - -There must be at most one call to `ic0.call_on_cleanup` between `ic0.call_new` and `ic0.call_perform`. - -- `ic0.call_with_best_effort_response : (timeout_seconds : i32) -> ()` - - Turns the call into a *bounded-wait call*, by relaxing the response delivery guarantee to be best effort, and asking the system to respond at the latest after `timeout_seconds` have elapsed. Best effort means the system may also respond with a `SYS_UNKNOWN` reject code, signifying that the call **may or may not** have been processed by the callee. Then, even if the callee produces a response, it will not be delivered to the caller. - - Any value for `timeout_seconds` is permitted, but is silently bounded from above by the `MAX_CALL_TIMEOUT` system constant; i.e., larger timeouts are treated as equivalent to `MAX_CALL_TIMEOUT` and do not cause an error. The implementation may add a specific [error code](#error-codes) to a reject message to indicate the cause, in particular whether the timeout expired. Note that the reject callback may be executed (possibly significantly) later than the specified time (e.g., if the caller is under high load), or before timeout expiration (e.g., if the system is under load). - - A caller that receives a `SYS_UNKNOWN` reject code, yet needs to learn the call outcome, must find an out-of-band way of doing so. For example, if the callee provides idempotent function calls, the caller can simply retry the call. Sample causes of `SYS_UNKNOWN` include the call not being delivered in time, call processing not completing in time, reply delivery taking too long, and the system shedding load. - - This method can be called only in between `ic0.call_new` and `ic0.call_perform`, and at most once at that. Otherwise, it traps. A different timeout can be specified for each call. - -- `ic0.call_data_append : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` - - Appends the specified bytes to the argument of the call. Initially, the argument is empty. Traps if the total appended data exceeds the [maximum inter-canister call payload](./cycles-costs.md). - - This may be called multiple times between `ic0.call_new` and `ic0.call_perform`. - -- `ic0.call_cycles_add : (amount : i64) -> ()` - - This system call moves cycles from the canister balance onto the call under construction, to be transferred with that call. - - The cycles are deducted from the balance as shown by `ic0.canister_cycle_balance128` immediately, and moved back if the call cannot be performed (e.g. if `ic0.call_perform` signals an error, if the canister invokes `ic0.call_new`, or returns without calling `ic0.call_perform`). - - This system call may be called multiple times between `ic0.call_new` and `ic0.call_perform`. - - This system call traps if there is no call under construction, i.e., if not called between `ic0.call_new` and `ic0.call_perform`. - - This system call traps if trying to transfer more cycles than returned by `ic0.canister_liquid_cycle_balance128`. - -- `ic0.call_cycles_add128 : (amount_high : i64, amount_low : i64) -> ()` - - This system call moves cycles from the canister balance onto the call under construction, to be transferred with that call. - - The amount of cycles it moves is represented by a 128-bit value which can be obtained by combining the `amount_high` and `amount_low` parameters. - - The cycles are deducted from the balance as shown by `ic0.canister_cycle_balance128` immediately, and moved back if the call cannot be performed (e.g. if `ic0.call_perform` signals an error, if the canister invokes `ic0.call_new`, or returns without calling `ic0.call_perform`). - - This system call may be called multiple times between `ic0.call_new` and `ic0.call_perform`. - - This system call traps if there is no call under construction, i.e., if not called between `ic0.call_new` and `ic0.call_perform`. - - This system call traps if trying to transfer more cycles than returned by `ic0.canister_liquid_cycle_balance128`. - -- `ic0.call_perform : () -> ( err_code : i32 )` - - This concludes assembling the call. It queues the call message to the given destination, but does not actually act on it until the current WebAssembly function returns without trapping. - - This deducts `MAX_CYCLES_PER_RESPONSE` cycles from the canister balance and sets them aside for response processing. - - The returned `err_code` is always one of `0` and `2`. An `err_code` of `0` means that no error occurred and that the IC could 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. - - A non-zero value of `err_code` (`2`) indicates that the call could not be performed and the semantics of that value are the same as for the corresponding `SYS_FATAL` [reject code](#reject-codes). The non-zero value of `err_code` (`2`) could arise due to a lack of resources within the IC, but also if the call would reduce the current cycle balance to a level below where the canister would be frozen. No callbacks are executed in this case. - - After `ic0.call_perform` and before the next call to `ic0.call_new`, all other `ic0.call_*` function calls trap. - -### Cycles {#system-api-cycles} - -Each canister maintains a balance of *cycles*, which are used to pay for platform usage. Cycles are represented by 128-bit values. - -:::note - -This specification currently does not go into details about which actions cost how many cycles and/or when. In general, you must assume that the canister's cycle balance can change arbitrarily between method executions, and during each System API function call, unless explicitly mentioned otherwise. - -::: - -- `ic0.canister_cycle_balance : () → i64` - - Indicates the current cycle balance of the canister. It is the canister balance before the execution of the current message, minus a reserve to pay for the execution of the current message and calls finalized via `ic0.call_perform`, minus any cycles queued up to be sent via `ic0.call_cycles_add` and `ic0.call_cycles_add128`. After execution of the message, the IC may add unused cycles from the reserve back to the balance. - -:::note - -This call traps if the current balance does not fit into a 64-bit value. Canisters that need to deal with larger cycles balances should use `ic0.canister_cycle_balance128` instead. - -::: - -- `ic0.canister_cycle_balance128 : (dst : I) → ()`; `I ∈ {i32, i64}` - - Indicates the current cycle balance of the canister by copying the value at the location `dst` in the canister memory. It is the canister balance before the execution of the current message, minus a reserve to pay for the execution of the current message and calls finalized via `ic0.call_perform`, minus any cycles queued up to be sent via `ic0.call_cycles_add` and `ic0.call_cycles_add128`. After execution of the message, the IC may add unused cycles from the reserve back to the balance. - -- `ic0.canister_liquid_cycle_balance128 : (dst : I) → ()`; `I ∈ {i32, i64}` - - Indicates the current amount of cycles that is available for spending in calls and execution by copying the value at the location `dst` in the canister memory. This amount of cycles can be safely attached to a call via `ic0.call_cycles_add128` as long as the memory usage of the canister does not increase for the rest of the current message execution. Hence, it is recommended to never attach the entire `ic0.canister_liquid_cycle_balance128` to a call, but leave some slack based on the expected canister memory usage and freezing threshold. - -- `ic0.msg_cycles_available : () → i64` - - Returns the amount of cycles that were transferred by the caller of the current call, and is still available in this message. - - Initially, in the update method entry point, this is the amount that the caller passed to the canister. When cycles are accepted (`ic0.msg_cycles_accept`), this reports fewer cycles accordingly. When the call is responded to (reply or reject), all available cycles are refunded to the caller, and this will return 0. - -:::note - -This call traps if the amount of cycles available does not fit into a 64-bit value. Please use `ic0.msg_cycles_available128` instead. - -::: - -- `ic0.msg_cycles_available128 : (dst : I) → ()`; `I ∈ {i32, i64}` - - Indicates the number of cycles transferred by the caller of the current call, still available in this message. The amount of cycles is represented by a 128-bit value. This call copies this value starting at the location `dst` in the canister memory. - - Initially, in the update method entry point, this is the amount that the caller passed to the canister. When cycles are accepted (`ic0.msg_cycles_accept128`), this reports fewer cycles accordingly. When the call is responded to (reply or reject), all available cycles are refunded to the caller, and this will report 0 cycles. - -- `ic0.msg_cycles_accept : (max_amount : i64) → (amount : i64)` - - This moves cycles from the call to the canister balance. It moves as many cycles as possible, up to these constraints: - - It moves no more cycles than `max_amount`. - - It moves no more cycles than available according to `ic0.msg_cycles_available`, and - - It can be called multiple times, each time possibly adding more cycles to the balance. - - The return value indicates how many cycles were actually moved. - - This system call does not trap. - -:::tip - -Example: To accept all cycles provided in a call, invoke `ic0.msg_cycles_accept(ic0.msg_cycles_available())` in the method handler or a callback handler, *before* calling reply or reject. - -::: - -- `ic0.msg_cycles_accept128 : (max_amount_high : i64, max_amount_low : i64, dst : I) → ()`; `I ∈ {i32, i64}` - - This moves cycles from the call to the canister balance. It moves as many cycles as possible, up to these constraints: - - It moves no more cycles than the amount obtained by combining `max_amount_high` and `max_amount_low`. Cycles are represented by 128-bit values. - - It moves no more cycles than available according to `ic0.msg_cycles_available128`, and - - It can be called multiple times, each time possibly adding more cycles to the balance. - - This call also copies the amount of cycles that were actually moved starting at the location `dst` in the canister memory. - - This does not trap. - -- `ic0.cycles_burn128 : (amount_high : i64, amount_low : i64, dst : I) -> ()`; `I ∈ {i32, i64}` - - This burns cycles from the canister. It burns as many cycles as possible, up to these constraints: - - It burns no more cycles than the amount obtained by combining `amount_high` and `amount_low`. Cycles are represented by 128-bit values. - - It burns no more cycles than the amount of cycles available for spending `liquid_balance(balance, reserved_balance, freezing_limit)`, where `reserved_balance` are cycles reserved for resource payments and `freezing_limit` is the amount of idle cycles burned by the canister during its `freezing_threshold`. - - It can be called multiple times, each time possibly burning more cycles from the balance. - - This call also copies the amount of cycles that were actually burned starting at the location `dst` in the canister memory. - - This system call does not trap. - -- `ic0.msg_cycles_refunded : () → i64` - - This function can only be used in a callback handler (reply or reject), and indicates the amount of cycles that came back with the response as a refund. The refund has already been added to the canister balance automatically. - -:::note - -This call traps if the amount of cycles refunded does not fit into a 64-bit value. In general, it is recommended to use `ic0.msg_cycles_refunded128` instead. - -::: - -- `ic0.msg_cycles_refunded128 : (dst : I) → ()`; `I ∈ {i32, i64}` - - This function can only be used in a callback handler (reply or reject), and indicates the amount of cycles that came back with the response as a refund. The refund has already been added to the canister balance automatically. - -### Stable memory {#system-api-stable-memory} - -:::note - -The 32-bit stable memory System API (`ic0.stable_size`, `ic0.stable_grow`, `ic0.stable_write`, and `ic0.stable_read`) is DEPRECATED. Canister developers are advised to use the 64-bit stable memory System API instead. - -::: - -Canisters have the ability to store and retrieve data from a secondary memory. The purpose of this *stable memory* is to provide space to store data beyond upgrades. The interface mirrors roughly the memory-related instructions of WebAssembly, and tries to be forward compatible with exposing this feature as an additional memory. - -The stable memory is initially empty and can be grown up to the [Wasm stable memory limit](./cycles-costs.md) (provided the subnet has capacity). - -- `ic0.stable_size : () → (page_count : i32)` - - returns the current size of the stable memory in WebAssembly pages. (One WebAssembly page is 64KiB) - - This system call traps if the size of the stable memory exceeds 232 bytes. - -- `ic0.stable_grow : (new_pages : i32) → (old_page_count : i32)` - - tries to grow the memory by `new_pages` many pages containing zeroes. - - This system call traps if the *previous* size of the memory exceeds 232 bytes. - - If the *new* size of the memory exceeds 232 bytes or growing is unsuccessful, then it returns `-1`. - - Otherwise, it grows the memory and returns the *previous* size of the memory in pages. - -- `ic0.stable_write : (offset : i32, src : i32, size : i32) → ()` - - copies the data referred to by `src`/`size` out of the canister and replaces the corresponding segment starting at `offset` in the stable memory. - - This system call traps if the size of the stable memory exceeds 232 bytes. - - It also traps if `src+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory. - -- `ic0.stable_read : (dst : i32, offset : i32, size : i32) → ()` - - copies the data referred to by `offset`/`size` out of the stable memory and replaces the corresponding bytes starting at `dest` in the canister memory. - - This system call traps if the size of the stable memory exceeds 232 bytes. - - It also traps if `dst+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory - -- `ic0.stable64_size : () → (page_count : i64)` - - returns the current size of the stable memory in WebAssembly pages. (One WebAssembly page is 64KiB) - -- `ic0.stable64_grow : (new_pages : i64) → (old_page_count : i64)` - - tries to grow the memory by `new_pages` many pages containing zeroes. - - If successful, returns the *previous* size of the memory (in pages). Otherwise, returns `-1`. - -- `ic0.stable64_write : (offset : i64, src : i64, size : i64) → ()` - - Copies the data from location \[src, src+size) of the canister memory to location \[offset, offset+size) in the stable memory. - - This system call traps if `src+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory. - -- `ic0.stable64_read : (dst : i64, offset : i64, size : i64) → ()` - - Copies the data from location \[offset, offset+size) of the stable memory to the location \[dst, dst+size) in the canister memory. - - This system call traps if `dst+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory. - -### System time {#system-api-time} - -The canister can query the IC for the current time. - -`ic0.time : () -> i64` - -The time is given as nanoseconds since 1970-01-01. The IC guarantees that - -- the time, as observed by the canister, is monotonically increasing, even across canister upgrades. - -- within an invocation of one entry point, the time is constant. - -The times observed by different canisters are unrelated, and calls from one canister to another may appear to travel "backwards in time". - -:::note - -While an implementation will likely try to keep the time returned by `ic0.time` close to the real time, this is not formally part of this specification. - -::: - -### Global timer - -The canister can set a global timer to make the system schedule a call to the exported `canister_global_timer` Wasm method after the specified time. The time must be provided as nanoseconds since 1970-01-01. - -`ic0.global_timer_set : (timestamp : i64) -> i64` - -The function returns the previous value of the timer. If no timer is set before invoking the function, then the function returns zero. - -Passing zero as an argument to the function deactivates the timer and thus prevents the system from scheduling calls to the canister's `canister_global_timer` Wasm method. - -### Performance counter {#system-api-performance-counter} - -The canister can query one of the "performance counters", which is a deterministic monotonically increasing integer approximating the amount of work the canister has done. Developers might use this data to profile and optimize the canister performance. - -`ic0.performance_counter : (counter_type : i32) -> i64` - -The argument `type` decides which performance counter to return: - -- 0 : current execution instruction counter. The number of WebAssembly instructions the canister has executed since the beginning of the current [Message execution](#rule-message-execution). - -- 1 : call context instruction counter. - - - For replicated message execution, it is the number of WebAssembly instructions the canister has executed within the call context of the current [Message execution](#rule-message-execution) since [Call context creation](#call-context-creation). The counter monotonically increases across all [message executions](#rule-message-execution) in the call context until the corresponding [call context is removed](#call-context-removal). - - - For non-replicated message execution, it is the number of WebAssembly instructions the canister has executed within the corresponding `composite_query_helper` in [Query call](#query-call). The counter monotonically increases across the executions of the composite query method and the composite query callbacks until the corresponding `composite_query_helper` returns (ignoring WebAssembly instructions executed within any further downstream calls of `composite_query_helper`). - -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. - -`ic0.in_replicated_execution : () -> (result: i32)` - -Returns 1 if the canister is being run in replicated mode and 0 otherwise. - -### Controller check {#system-api-controller-check} - -The canister can check whether a given principal is one of its controllers. - -`ic0.is_controller : (src : I, size : I) -> (result: i32)`; `I ∈ {i32, i64}` - -Checks whether the principal identified by `src`/`size` is one of the controllers of the canister. If yes, then a value of 1 is returned, otherwise a 0 is returned. It can be called multiple times. - -This system call traps if `src+size` exceeds the size of the WebAssembly memory or the principal identified by `src`/`size` is not a valid binary encoding of a principal. - -### Certified data {#system-api-certified-data} - -For each canister, the IC keeps track of "certified data", a canister-defined blob. For fresh canisters (upon install or reinstall), this blob is the empty blob (`""`). - -- `ic0.root_key_size: () → I` and `ic0.root_key_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - Copies the public key (a DER-encoded BLS key) of the IC root key of this instance of the Internet Computer Protocol to the canister. - - This traps in non-replicated mode (NRQ, TQ, CQ, CRy, CRt, CC, and F modes, as defined in [Overview of imports](#system-api-imports)). - -- `ic0.certified_data_set : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` - - The canister can update the certified data with this call. The passed data must be no larger than 32 bytes. This can be used any number of times. - -When executing a query or composite query method via a query call (i.e. in non-replicated mode), the canister can fetch a certificate that authenticates to third parties the value last set via `ic0.certified_data_set`. The certificate is not available in composite query method callbacks and in query and composite query methods evaluated on canisters other than the target canister of the query call. - -- `ic0.data_certificate_present : () -> i32` - - returns `1` if a certificate is present, and `0` otherwise. - - This will return `1` when called from a query or composite query method on the target canister of a query call. - - This will return `0` for update methods, if a query method is executed in replicated mode (e.g. when invoked via an update call or inter-canister call) or as canister http outcall transform, and in composite query method callbacks and in query and composite query methods evaluated on canisters other than the target canister of a query call. - -- `ic0.data_certificate_size : () → I` and `ic0.data_certificate_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` - - Copies the certificate for the current value of the certified data to the canister. - - The certificate is a blob as described in [Certification](#certification) that contains the values at path `/canister//certified_data` and at path `/time` of [The system state tree](#state-tree). - - If this `certificate` includes a subnet delegation, then the id of the current canister will be included in the delegation's canister id range. - - This traps if `ic0.data_certificate_present()` returns `0`. - -### Cycle cost calculation {#system-api-cycle-cost} - -Inter-canister calls have an implicit cost, and some calls to the management canister require the caller to attach cycles to the call explicitly. -The various cost factors may change over time, so the following system calls give the canister programmatic, up-to-date information about the costs. - -These system calls return costs in Cycles, represented by 128 bits, which will be written to the heap memory starting at offset `dst`. Note that the cost calculation is only correct for correct inputs, e.g., a method name length argument should not exceed 20'000, because such an argument would be rejected by `ic0.call_new`. The cost API will still return a number in this case, but it would not have a real meaning. - -- `ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> ()`; `I ∈ {i32, i64}` - - This system call returns the amount of cycles that a canister needs to be above the freezing threshold in order to successfully make an inter-canister call. This includes the base cost for an inter-canister call, the cost for each byte transmitted in the request, the cost for the transmission of the largest possible response, and the cost for executing the largest possible response callback. The last two are cost _reservations_, which must be possible for a call to succeed, but they will be partially refunded if the real response and callback are smaller. So the cost of the actual inter-canister call may be less than this system call predicts, but it cannot be more. - `method_name_size` is the byte length of the method name, and `payload_size` is the byte length of the argument to the method. - -- `ic0.cost_create_canister : (dst : I) -> ()`; `I ∈ {i32, i64}` - - The cost of creating a canister on the same subnet as the calling canister via [`create_canister`](#ic-create_canister). Note that canister creation via a call to the CMC can have a different cost if the target subnet has a different replication factor. - -- `ic0.cost_http_request(request_size : i64, max_res_bytes : i64, dst : I) -> ()`; `I ∈ {i32, i64}` - - The cost of a canister http outcall via [`http_request`](#ic-http_request). `request_size` is the sum of the byte lengths of the following components of an http request: - - url - - headers - i.e., the sum of the lengths of all keys and values - - body - - transform - i.e., the sum of the transform method name length and the length of the transform context - - `max_res_bytes` is the maximum response length the caller wishes to accept (the caller should provide the default value of `2,000,000` if no maximum response length is provided in the actual request to the management canister). - -- `ic0.cost_sign_with_ecdsa(src : I, size : I, ecdsa_curve: i32, dst : I) -> i32`; `I ∈ {i32, i64}` - -- `ic0.cost_sign_with_schnorr(src : I, size : I, algorithm: i32, dst : I) -> i32`; `I ∈ {i32, i64}` - -- `ic0.cost_vetkd_derive_key(src : I, size : I, vetkd_curve: i32, dst : I) -> i32`; `I ∈ {i32, i64}` - - These system calls accept a key name via a textual representation for the specific signing scheme / key of a given size stored in the heap memory starting at offset `src`. They also accept an `i32` with the following interpretations: - - `ecdsa_curve: 0 → secp256k1` - - `algorithm: 0 → bip340secp256k1, 1 → ed25519` - - `vetkd_curve: 0 → bls12_381` - - See [`sign_with_ecdsa`](#ic-sign_with_ecdsa), [`sign_with_schnorr`](#ic-sign_with_schnorr) and [`vetkd_encrypted_key`](#ic-vetkd_encrypted_key) for more information. - - These system calls trap if `src` + `size` or `dst` + 16 exceed the size of the WebAssembly memory. Otherwise, they return an `i32` with the following meaning: - - `0`: Success. The result can be found at the memory address `dst`. - - `1`: Invalid curve or algorithm. Memory at `dst` is left unchanged. - - `2`: Invalid key name for the given combination of signing scheme and (valid) curve/algorithm. Memory at `dst` is left unchanged. - -### Environment Variables - -The following system calls provide access to the canister's environment variables: - -- `ic0.env_var_count : () -> I`; `I ∈ {i32, i64}` - - Returns the number of environment variables set for this canister. - -- `ic0.env_var_name_size : (index: I) -> I`; `I ∈ {i32, i64}` - - Gets the size in bytes of the name of the environment variable at the given index. - - This system call traps if: - - If the index is out of bounds (>= than value provided by `ic0.env_var_count`) - -- `ic0.env_var_name_copy : (index: I, dst: I, offset: I, size: I) -> ()`; `I ∈ {i32, i64}` - - Copies the name of the environment variable at the given index into memory. - - This system call traps if: - - The index is out of bounds (>= than value provided by `ic0.env_var_count`) - - `offset+size` is greater than the size of the environment variable name - - `dst+size` exceeds the size of the WebAssembly memory - - -- `ic0.env_var_name_exists : (name_src: I, name_size: I) -> i32`; `I ∈ {i32, i64}` - - Checks if an environment variable with the given name exists. If yes, then a value of 1 is returned, otherwise a 0 is returned. - - This system call traps if: - - `name_size` exceeds the maximum length of a variable name - - `name_src+name_size` exceeds the size of the WebAssembly memory - - If the data referred to by `name_src`/`name_size` is not valid UTF8. - - -- `ic0.env_var_value_size : (name_src: I, name_size: I) -> I`; `I ∈ {i32, i64}` - - Gets the size in bytes of the value for the environment variable with the given name. - - This system call traps if: - - `name_size` exceeds the maximum length of a variable name - - `name_src+name_size` exceeds the size of the WebAssembly memory - - If the data referred to by `name_src`/`name_size` is not valid UTF8. - - The name does not match any existing environment variable. - -- `ic0.env_var_value_copy : (name_src: I, name_size: I, dst: I, offset: I, size: I) -> ()`; `I ∈ {i32, i64}` - - Copies the value of the environment variable with the given name into memory. - - This system call traps if: - - `name_size` exceeds the maximum length of a variable name - - `name_src+name_size` exceeds the size of the WebAssembly memory - - If the data referred to by `name_src`/`name_size` is not valid UTF8. - - The name does not match any existing environment variable. - - `offset+size` is greater than the size of the environment variable value - - `dst+size` exceeds the size of the WebAssembly memory - -These system calls allow canisters to: -- Enumerate all environment variables -- Look up values by name - -### Debugging aids - -Canister can produce logs available through the management canister endpoint [`fetch_canister_logs`](#ic-fetch_canister_logs). - -- `ic0.debug_print : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` - - This copies out the data specified by `src` and `size` and appends that data to canister logs. - The data can be trimmed to an implementation defined maximum size. - - This function never traps, even if the `src+size` exceeds the size of the memory (in which case system-generated data are used instead). - -Similarly, the System API allows the canister to effectively trap and give some indication about why it trapped: - -- `ic0.trap : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` - - This copies out the data specified by `src` and `size` and appends that data to canister logs. - The data can be trimmed to an implementation defined maximum size. - - Moreover, the data specified by `src` and `size` might be included in a reject message - (trimmed to an implementation defined maximum size and omitting bytes that are not valid UTF-8). - - This function always traps. - -### Outlook: Using Host References {#host-references} - -The Internet Computer aims to make the most of the WebAssembly platform, and embraces WebAssembly features. With WebAssembly host references, we can make the platform more secure, the interfaces more abstract and more compositional. The above `ic0` System API does not yet use WebAssembly host references. Once they become available on our platform, a new version of the System API using host references will be available via the `ic` module. The changes will be, at least - -1. The introduction of a `api_nonce` reference, which models the capability to use the System API. It is passed as an argument to `canister_init`, `canister_update ` etc., and expected as an argument by almost all System API function calls. (The debugging aids remain unconstrained.) - -2. The use of references, instead of binary blobs, to address principals (user ids, canister ids), e.g. in `ic0.msg_caller` or in `ic0.call_new`. Additional functions will be provided to convert between the transparent binary representation of principals and references. - -3. Making the builder interface to create calls build calls identified by a reference, rather than having an implicit partial call in the background. - -A canister may only use the old *or* the new interface; the IC detects which interface the canister intends to use based on the names and types of its function imports and exports. - -## The IC management canister {#ic-management-canister} - -The interfaces above provide the fundamental ability for external users and canisters to contact other canisters. But the Internet Computer provides additional functionality, such as canister and user management. This functionality is exposed to external users and canisters via the *IC management canister*. - -:::note - -The *IC management canister* is just a facade; it does not actually exist as a canister (with isolated state, Wasm code, etc.). - -::: - -The IC management canister address is `aaaaa-aa` (i.e. the empty blob). - -It is possible to use the management canister via external requests (a.k.a. ingress messages). The cost of processing that request is charged to the canister that is being managed. Most methods only permit the controllers to call them. Calls to `raw_rand` and `deposit_cycles` are never accepted as ingress messages. - -### Interface overview {#ic-candid} - -The [interface description](_attachments/ic.did) below, in [Candid syntax](https://github.com/dfinity/candid/blob/master/spec/Candid.md), describes the available functionality. -``` candid name= ic-interface file file=_attachments/ic.did -``` - -The binary encoding of arguments and results are as per Candid specification. - -### IC method `create_canister` {#ic-create_canister} - -This method can only be called by canisters and subnet admins, i.e., it cannot be called by external users who are not subnet admins via ingress messages. - -Before deploying a canister, the administrator of the canister first has to register it with the IC, to get a canister id (with an empty canister behind it), and then separately install the code. - -The optional `settings` parameter can be used to set the following settings: - -- `controllers` (`vec principal`) - - A list of at most 10 principals. The principals in this list become the *controllers* of the canister. - Note that the caller of the `create_canister` call is not a controller of the canister - unless it is a member of the `controllers` list. - - Default value: A list containing only the caller of the `create_canister` call. - -- `compute_allocation` (`nat`) - - Must be a number between 0 and 100, inclusively. It indicates how much compute power should be guaranteed to this canister, expressed as a percentage of the maximum compute power that a single canister can allocate. If the IC cannot provide the requested allocation, for example because it is oversubscribed, the call will be rejected. - - Default value: 0 - -- `memory_allocation` (`nat`) - - Must be a number between 0 and 264-1, inclusively. - It indicates an amount of memory in bytes that the canister is guaranteed to be allowed to use in total. - If the IC cannot guarantee the requested memory allocation, for example because it is oversubscribed, then the call will be rejected. - - Default value: 0 - -- `freezing_threshold` (`nat`) - - Must be a number between 0 and 264-1, inclusively, and indicates a length of time in seconds. - - A canister is considered frozen whenever the IC estimates that the canister would be depleted of cycles before `freezing_threshold` seconds pass, given the canister's current size and the IC's current cost for storage. - - Calls to a frozen canister will be rejected with `SYS_TRANSIENT` reject code. Additionally, a canister cannot perform calls if that would, due the cost of the call and transferred cycles, would push the balance into frozen territory; these calls fail with `ic0.call_perform` returning a non-zero error code. - - Default value: 2592000 (approximately 30 days). - -- `reserved_cycles_limit` (`nat`) - - Must be a number between 0 and 2128-1, inclusively, and indicates the upper limit on `reserved_cycles` of the canister. - - An operation that allocates resources such as compute and memory will fail if the new value of `reserved_cycles` exceeds this limit. - - Default value: 5_000_000_000_000 (5 trillion cycles). - -- `wasm_memory_limit` (`nat`) - - Must be a number between 0 and 248-1 (i.e., 256TB), inclusively, and indicates the upper limit on the WASM heap memory consumption of the canister in bytes. - - An operation (update method, canister init, canister post_upgrade) that causes the WASM heap memory consumption to exceed this limit will trap. - The WASM heap memory limit is ignored for query methods, response callback handlers, global timers, heartbeats, and canister pre_upgrade. - - If set to 0, then there's no upper limit on the WASM heap memory consumption of the canister subject to the available memory on the IC. - - Default value: 0 (i.e., no explicit limit). - - Note: in a future release of this specification, the default value and whether the limit is enforced for global timers and heartbeats might change. - -- `log_visibility` (`log_visibility`) - - Controls who can access the canister's logs through the `fetch_canister_logs` endpoint of the management canister. Can be one of: - - `controllers`: Only the canister's controllers can fetch logs - - `public`: Anyone can fetch the canister's logs - - `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can fetch logs, the maximum length of the list is 10 - - Default value: `controllers`. - -- `snapshot_visibility` (`snapshot_visibility`) - - Controls who can access the canister's snapshots through the following endpoints of the management canister: - - `read_canister_snapshot_metadata` - - `read_canister_snapshot_data` - - `list_canister_snapshots` - - Can be one of: - - `controllers`: Only the canister's controllers can read its snapshots - - `public`: Anyone can read the canister's snapshots - - `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can read its snapshots, the maximum length of the list is 10 - - Default value: `controllers`. - -- `wasm_memory_threshold` (`nat`) - - Must be a number between 0 and 264-1, inclusively, and indicates the threshold on the remaining wasm memory size of the canister in bytes: - if the remaining wasm memory size of the canister is below the threshold, execution of the ["on low wasm memory" hook](#on-low-wasm-memory) is scheduled. - - Default value: 0 (i.e., the "on low wasm memory" hook is never scheduled). - -- `environment_variables` (`environment_variables`) - - A record containing key-value pairs where both key and value are UTF-8 encoded strings. These variables are accessible to the canister during execution and can be used to configure canister behavior without code changes. Each key must be unique. - - The maximum number of environment variables is implementation-defined. The maximum length of keys and values is implementation-defined. - - Default value: `null` (i.e., no environment variables provided). - -The optional `sender_canister_version` parameter can contain the caller's canister version. If provided, its value must be equal to `ic0.canister_version`. - -Until code is installed, the canister is `Empty` and behaves like a canister that has no public methods. - -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). (No cycles are required on subnets that have a non-empty list of subnet admins.) - -### IC method `update_settings` {#ic-update_settings} - -This method can be called by canisters as well as by external users via ingress messages. - -Only *controllers* of the canister can update settings. See [IC method](#ic-create_canister) for a description of settings. - -Not including a setting in the `settings` record means not changing that field. The defaults described above are only relevant during canister creation. - -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 `upload_chunk` {#ic-upload_chunk} - -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. - -### IC method `stored_chunks` {#ic-stored_chunks} - -This method can be called by canisters as well as by external users via ingress messages. - -Canister controllers (and the canister itself) can list the hashes of chunks in the chunk storage of a canister. - -### IC method `install_code` {#ic-install_code} - -This method can be called by canisters as well as by external users via ingress messages. - -This method installs code into a canister. - -Only controllers of the canister can install code. - -- If `mode = variant { install }`, the canister must be empty before. This will instantiate the canister module and invoke its `canister_init` method (if present), as explained in Section "[Canister initialization](#system-api-init)", passing the `arg` to the canister. - -- If `mode = variant { reinstall }`, if the canister was not empty, its existing code and state (including stable memory) is removed before proceeding as for `mode = install`. - - Note that this is different from `uninstall_code` followed by `install_code`, as `uninstall_code` generates a synthetic reject response to all callers of the uninstalled canister that the uninstalled canister did not yet reply to and ensures that callbacks to outstanding calls made by the uninstalled canister won't be executed (i.e., upon receiving a response from a downstream call made by the uninstalled canister, the cycles attached to the response are refunded, but no callbacks are executed). - -- If `mode = variant { upgrade }` or `mode = variant { upgrade = opt record { skip_pre_upgrade = .., wasm_memory_persistence = .. } }`, this will perform an upgrade of a non-empty canister as described in [Canister upgrades](#system-api-upgrades), passing `arg` to the `canister_post_upgrade` method of the new instance. If `skip_pre_upgrade = opt true`, then the `canister_pre_upgrade` method on the old instance is not executed. If `wasm_memory_persistence = opt keep`, then the WebAssembly memory is preserved. - -This is atomic: If the response to this request is a `reject`, then this call had no effect. - -:::note - -Some canisters may not be able to make sense of callbacks after upgrades; these should be stopped first, to wait for all outstanding callbacks, or be uninstalled first, to prevent outstanding callbacks from being invoked (by marking the corresponding call contexts as deleted). It is expected that the canister admin (or their tooling) does that separately. - -::: - -The `wasm_module` field specifies the canister module to be installed. The system supports multiple encodings of the `wasm_module` field, as described in [Canister module format](#canister-module-format): - -- If the `wasm_module` starts with byte sequence `[0x00, 'a', 's', 'm']`, the system parses `wasm_module` as a raw WebAssembly binary. - -- If the `wasm_module` starts with byte sequence `[0x1f, 0x8b, 0x08]`, the system parses `wasm_module` as a gzip-compressed WebAssembly binary. - -The optional `sender_canister_version` parameter can contain the caller's canister version. If provided, its value must be equal to `ic0.canister_version`. - -This method traps if the canister's cycle balance decreases below the canister's freezing limit after executing the method. - -### IC method `install_chunked_code` {#ic-install_chunked_code} - -This method can be called by canisters as well as by external users via ingress messages. - -This method installs code that had previously been uploaded in chunks. - -Only controllers of the target canister can call this method. - -The `mode`, `arg`, and `sender_canister_version` parameters are as for `install_code`. -The `target_canister` specifies the canister where the code should be installed. -The optional `store_canister` specifies the canister in whose chunk storage the chunks are stored (this parameter defaults to `target_canister` if not specified). -For the call to succeed, the caller must be a controller of the `store_canister` or the caller must be the `store_canister`. The `store_canister` must be on the same subnet as the target canister. - -The `chunk_hashes_list` specifies a list of hash values `[h1,...,hk]` with `k <= MAX_CHUNKS_IN_LARGE_WASM`. The system looks up in the chunk store of `store_canister` (or that of the target canister if `store_canister` is not specified) blobs corresponding to `h1,...,hk` and concatenates them to obtain a blob of bytes referred to as `wasm_module` in `install_code`. It then checks that the SHA-256 hash of `wasm_module` is equal to the `wasm_module_hash` parameter and calls `install_code` with parameters `(record {mode; target_canister; wasm_module; arg; sender_canister_version})`. - -### IC method `uninstall_code` {#ic-uninstall_code} - -This method can be called by canisters as well as by external users via ingress messages. - -This method removes a canister's code and state, making the canister *empty* again. - -Only controllers of the canister or subnet admins can uninstall code. - -Uninstalling a canister's code will reject all calls that the canister has not yet responded to, and drop the canister's code and state. Outstanding responses to the canister will not be processed, even if they arrive after code has been installed again. Cycles attached to such responses will still be refunded though. - -The canister is now [empty](#canister-lifecycle). In particular, any incoming or queued calls will be rejected. - -A canister after *uninstalling* retains its *cycle* balances, *controllers*, history, status, and allocations. - -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 `canister_status` {#ic-canister_status} - -This method can be called by canisters as well as by external users via ingress messages. -This method can also be called by external users via non-replicated (query) calls, but it cannot be called from composite query calls. - -Indicates various information about the canister. It contains: - -- The status of the canister. It could be one of `running`, `stopping` or `stopped`. - -- A bool `ready_for_migration` indicating whether a stopped canister is ready to be migrated to another subnet (i.e., whether it has empty queues and flushed streams). This flag can only ever be `true` if the `status` variant (see above) is `stopped`. This property is guaranteed by the protocol, but deliberately not on the type level in order to facilitate backwards compatible service evolution. - -- The canister version. - -- The "settings" of the canister containing: - - - The controllers of the canister. The order of returned controllers may vary depending on the implementation. - - - The compute allocation of the canister. - - - The memory allocation of the canister in bytes. - - - The freezing threshold of the canister in seconds. - - - The reserved cycles limit of the canister, i.e., the maximum number of cycles that can be in the canister's reserved balance after increasing the canister's memory allocation and/or actual memory usage. - - - The visibility of the canister's logs. - - - The visibility of the canister's snapshots. - - - The WASM heap memory limit of the canister in bytes (the value of `0` means that there is no explicit limit). - - - The "low wasm memory" threshold, which is used to determine when the [canister_on_low_wasm_memory](#on-low-wasm-memory) function is executed. - - - The environment variables of the canister, which is a record containing key-value pairs used to configure the canister's behavior. - -- A SHA256 hash of the module installed on the canister. This is `null` if the canister is empty. - -- The actual memory usage of the canister, representing the total memory consumed by the canister. - -- A record containing detailed breakdown of memory usage into individual components (see [Memory Metrics](#ic-canister_status-memory_metrics) for more details). - -- The cycle balance of the canister. - -- The reserved cycles balance of the canister, i.e., the number of cycles reserved when increasing the canister's memory allocation and/or actual memory usage. - -- The idle cycle consumption of the canister, i.e., the number of cycles burned by the canister per day due to its compute and memory allocation and actual memory usage. - -- Statistics regarding the query call execution of the canister, i.e., a record containing the following fields: - - * `num_calls_total`: the total number of query and composite query methods evaluated on the canister, - - * `num_instructions_total`: the total number of WebAssembly instructions executed during the evaluation of query and composite query methods, - - * `request_payload_bytes_total`: the total number of query and composite query method payload bytes, and - - * `response_payload_bytes_total`: the total number of query and composite query response payload (reply data or reject message) bytes. - -Only the controllers of the canister or the canister itself or subnet admins can request its status. - -#### Memory Metrics {#ic-canister_status-memory_metrics} - - * `wasm_memory_size`: Represents the Wasm memory usage of the canister, i.e. the heap memory used by the canister's WebAssembly code. - - * `stable_memory_size`: Represents the stable memory usage of the canister. - - * `global_memory_size`: Represents the memory usage of the global variables that the canister is using. - - * `wasm_binary_size`: Represents the memory occupied by the Wasm binary that is currently installed on the canister. This is the size of the binary uploaded via `install_code` or `install_chunked_code`, e.g., the compressed size if the uploaded binary is gzipped. - - * `custom_sections_size`: Represents the memory used by custom sections defined by the canister, which may include additional metadata or configuration data. - - * `canister_history_size`: Represents the memory used for storing the canister's history. - - * `wasm_chunk_store_size`: Represents the memory used by the Wasm chunk store of the canister. - - * `snapshots_size`: Represents the memory consumed by all snapshots that belong to this canister. - -All sizes are expressed in bytes. - -### 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. - -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, loading a snapshot, controllers change, canister renaming). 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). -- Canister creation is described by the full set of controllers along with a [hash of the environment variables](#hash-of-map), if environment variables were specified. The order of controllers stored in the canister history may vary depending on the implementation. -- Code deployment is described by its 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 ID of the canister from which the snapshot was loaded (if that canister ID is different than the canister ID onto which the snapshot is loaded), the snapshot ID, the canister version and timestamp at which the snapshot was taken (the canister version and timestamp refer to the canister from which the snapshot was loaded), and the source of the snapshot (canister state or metadata upload). -- Controllers change is described by the full new set of controllers after the change. The order of controllers stored in the canister history may vary depending on the implementation. -- Canister renaming is described by the canister ID and the total number of canister changes before renaming as well as the canister ID, the canister version, and the total number of canister changes of the new canister ID. Because only a dedicated NNS canister can perform canister renaming, the actual principal who requested canister renaming is recorded in a separate field `requested_by`. The total number of canister changes reported by the IC method `canister_info` is overriden to the total number of canister changes of the new canister ID. Canister changes referring to the canister ID before renaming are preserved. - -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. - -The following parameters should be supplied for the call: - -- `canister_id`: the canister ID of the canister to retrieve information about. - -- `num_requested_changes`: optional, specifies the number of requested canister changes. If not provided, the default value of `0` will be used. - -The returned response contains the following fields: - -- `total_num_changes`: the total number of canister changes that have been ever recorded in the history. This value does not change if the system drops the oldest canister changes from the list of changes. - -- `recent_changes`: the list containing the most recent canister changes. If `num_requested_changes` is provided, then this list contains that number of changes or, if more changes are requested than available in the history, then this list contains all changes available in the history. If `num_requested_changes` is not specified, then this list is empty. - -- `module_hash`: the SHA-256 hash of the currently installed canister module (or `null` if the canister is empty). - -- `controllers`: the current set of canister controllers. The order of returned controllers may vary depending on the implementation. - -### IC method `canister_metadata` {#ic-canister_metadata} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -Provides access to canister's metadata contained in custom sections whose names have the form `icp:public ` or `icp:private ` -(see [Canister module format](#canister-module-format) for requirements on custom sections). - -Every canister can retrieve public metadata (in custom sections whose names have the form `icp:public `) of every other canister (including itself). -Only controllers of a canister can access its private metadata (in custom sections whose names have the form `icp:private `). - -The following parameters should be supplied for the call: - -- `canister_id` (`principal`): the canister ID of the canister to retrieve metadata from. - -- `name` (`text`): identifies canister's metadata contained in a custom section whose name has the form `icp:public ` or `icp:private ` - (note that a canister cannot have custom sections with both `icp:public ` or `icp:private ` as names for the same ``, see [Canister module format](#canister-module-format)). - -The returned response contains the following fields: - -- `value` (`blob`): the content of canister's metadata identified by the given `name`. - -### IC method `stop_canister` {#ic-stop_canister} - -This method can be called by canisters as well as by external users via ingress messages. - -The controllers of a canister or subnet admins may stop a canister (e.g., to prepare for a canister upgrade). - -When this method successfully returns, then the canister status is `stopped` at that point. -However, note that the canister might be restarted at any time due to a concurrent call. - -The execution of this method proceeds as follows: - -- The immediate effect is that the status of the canister is changed to `stopping` (unless the canister is already stopped). -- The IC now rejects all calls to a stopping canister, indicating that the canister is stopping. Responses to a stopping canister are processed as usual. -- When all outstanding responses have been processed (so that there are no open call contexts), the canister status is changed to `stopped`. -- If the canister status is changed to `stopped` within an implementation-specific timeout, then this method successfully returns. -- Otherwise, this method returns an error (the canister status is still `stopping` and might eventually become `stopped` if all outstanding responses have been processed and the canister has not been restarted by a separate call). - -### IC method `start_canister` {#ic-start_canister} - -This method can be called by canisters as well as by external users via ingress messages. - -A canister may be started by its controllers or subnet admins. - -If the canister status was `stopped` or `stopping` then the canister status is simply set to `running`. In the latter case all `stop_canister` calls which are processing fail (and are rejected). - -If the canister was already `running` then the status stays unchanged. - -### IC method `delete_canister` {#ic-delete_canister} - -This method can be called by canisters as well as by external users via ingress messages. - -This method deletes a canister from the IC. - -Only controllers of the canister or subnet admins can delete it and the canister must already be stopped. Deleting a canister cannot be undone, any state stored on the canister is permanently deleted and its cycles are discarded. Once a canister is deleted, its ID cannot be reused. - -### IC method `deposit_cycles` {#ic-deposit_cycles} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -This method deposits the cycles included in this call into the specified canister. - -### IC method `raw_rand` {#ic-raw_rand} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -This method takes no input and returns 32 pseudo-random bytes to the caller. The return value is unknown to any part of the IC at time of the submission of this call. A new return value is generated for each call to this method. - -### IC method `ecdsa_public_key` {#ic-ecdsa_public_key} - -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 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 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} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -This method returns a new [ECDSA](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. This public key can be obtained by calling `ecdsa_public_key` with the caller's `canister_id`, and the same `derivation_path` and `key_id` used here. - -:::note - -If the signing request returns a reject response whose reject code is `SYS_UNKNOWN` or `CANISTER_ERROR`, the signature may exist in the system even though it's not returned to the requesting canister. Thus, canisters should not rely on the signature not existing in these cases. - -::: - -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 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} - -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} - -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`. - -:::note - -If the signing request returns a reject response whose reject code is `SYS_UNKNOWN` or `CANISTER_ERROR`, the signature may exist in the system even though it's not returned to the requesting canister. Thus, canisters should not rely on the signature not existing in these cases. - -::: - -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). Note that the returned signature is **non-deterministic** and a different signature may be computed every time it is requested. - -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 rejected. - -This call accepts an optional auxiliary parameter `aux`. The auxiliary parameter type `schnorr_aux` is an enumeration. The only currently supported variant is `bip341` which allows passing a Merkle tree root hash, which is required to implement Taproot signatures as defined in [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki). The `bip341` variant is only allowed for `bip340secp256k1` signatures, and if provided the `merkle_root_hash` must be generated in accordance with BIP341's specification for `taproot_output_script`. Specifically it should be either an empty bytestring (for the `script == None` case) or else 32 bytes generated using the procedure documented as `taproot_tree_helper`. If no auxiliary parameter is provided, then `bip340secp256k1` signatures are generated in accordance with BIP340. - -On the Internet Computer, the tuple of the requested master key, the calling canister, and derivation path determines which private key is used to generate the signature, and which public key is returned by `schnorr_public_key`. - -When using BIP341 signatures, the actual signature that is created will be relative to the Schnorr signature derived as described in BIP341's `taproot_sign_script`. The key returned by `schnorr_public_key` is the value identified in BIP341 as `internal_pubkey`. - -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 `vetkd_public_key` {#ic-vetkd_public_key} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -This method returns the vetKD public (verification) key derived from the vetKD master public key with ID `key_id` for the canister with the given `canister_id` and the given `context`. - -If the `canister_id` is unspecified, it will default to the canister id of the caller. The `context` is a byte string of variable length. The `key_id` is a struct specifying both a curve and a name. The availability of a particular `key_id` depends on the implementation. - -The public key returned for an empty `context` is called _canister public key_. Given this canister public key, the public key for a particular `context` can also be derived offline. - -For curve `bls12_381_g2`, the returned `public_key` is a G2 element in compressed form in [BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381) encoding. - -This call requires that a vetKD master key with ID `key_id` was generated by the IC and the key derivation functionality for that key was enabled, and that the `canister_id` meets the requirement of a canister id. Otherwise, the call is is rejected. - -### IC method `vetkd_derive_key` {#ic-vetkd_derive_key} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -This method returns a vetKD key (aka vetKey) encrypted under `transport_public_key` and derived from the vetKD master key with ID `key_id` based on the caller's `input` for a given `context`. - -Both the `input` and the `context` are byte strings of variable length. While both are inputs to the underlying key derivation algorithm (implicitly together with the calling canister's ID), `input` is intended as the primary differentiator when deriving different keys, while `context` is intended as domain separator. -The `key_id` is a struct specifying both a curve and a name. The availability of a particular `key_id` depends on the implementation. - -Both the encrypted and the decrypted form of the vetKD key can be verified by using the respective vetKD public (verification) key, which can be obtained by calling the IC method `vetkd_public_key`. - -For curve `bls12_381_g2`, the following holds: - -- The `transport_public_key` is a G1 element in compressed form in [BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381) encoding. Transport public keys are created by calculating *tpk = g1tsk*, where the transport secret key *tsk* is chosen uniformly at random from Zp. - -- The returned `encrypted_key` is the blob `E1 · E2 · E3`, where E1 and E3 are G1 elements, and E2 is a G2 element, all in compressed form in [BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381) encoding. - - The encrypted key can be verified by ensuring *e(E1, g2) == e(g1, E2)*, and *e(E3, g2) == e(tpk, E2) \* e(H(dpk · `input`), dpk)*, where *dpk* is the derived (vetKD) public key associated with the respective `context`, `key_id`, and the canister ID of the caller. - -- The decrypted vetKD key *k* is obtained by calculating E3 \* E1-tsk, where tsk ∈ Zp is the transport secret key that was used to generate the `transport_public_key`. - - The key can be verified by ensuring *e(k, g2) == e(H(dpk · `input`), dpk)*, where *dpk* is the derived (vetKD) public key associated with the respective `context`, `key_id`, and the canister ID of the caller. Such verification protects against untrusted canisters returning invalid keys. - -where - -- g1, g2 are generators of G1, G2, which are groups of prime order *p*, - -- \* denotes the group operation in G1, G2, and GT, - -- e: `G1 x G2 → GT` is the pairing (see [BLS Signatures Draft RFC, Appendix A](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381)), - -- H hashes into G1 according to the [BLS12-381 message augmentation scheme ciphersuite in the BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature#name-message-augmentation-2) (see also [Hashing to Elliptic Curves Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve#name-suites-for-bls12-381)), - -- `·` and · denote concatenation - -This call requires that a vetKD master key with ID `key_id` was generated by the IC and the key derivation 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). - -### IC method `http_request` {#ic-http_request} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -This method makes an HTTP request to a given URL and returns the HTTP response, possibly after a transformation. - -The method can be called in either replicated or non-replicated mode. In the replicated mode, the same HTTP request is performed by multiple IC replicas, providing strong guarantees on the integrity of the response. In the non-replicated mode, the request is made by a single replica, with weak integrity guarantees. - -:::note - -The non-replicated mode is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. - -::: - -Both because of replication and to handle network issues, the canister should aim to issue *idempotent* requests, meaning that it must not change the state at the remote server, or that the remote server has the means to identify duplicated requests. Otherwise, the risk of failure increases. - -In the replicated mode, the responses for all identical requests must match, too. However, a web service could return slightly different responses for identical idempotent requests. For example, it may include some unique identification or a timestamp that would vary across responses. - -For this reason, the calling canister can supply a transformation function, which the IC uses to let the canister sanitize the responses from such unique values. The transformation function is executed separately on the corresponding response received for a request (both in replicated and non-replicated modes). Only the transformed response will be available to the calling canister. - -Currently, the `GET`, `HEAD`, and `POST` methods are supported for HTTP requests. Additionally, the `PUT` and `DELETE` methods are supported in non-replicated mode only. `PUT` and `DELETE` are restricted to non-replicated mode to avoid confusing race conditions that may occur with replicated execution. - -It is important to note the following for the usage of the `POST` method: - -- The calling canister must make sure that the remote server is able to recognize requests as duplicates of each other and apply only one of them, even if they are sent from multiple sources. This may require, for example, to set a certain request header to uniquely identify the request. This is especially important in the replicated mode. - -- There is no guarantee that all sent requests are as specified by the canister. - -Furthermore, for all methods, the following holds: - -- There are no confidentiality guarantees on the request or response content. - -- In the replicated mode, if the canister receives a response, then at least one request that was sent matched the canister's request, and the response was to that request. In the non-replicated mode, there are no such guarantees. The canister should not assume the integrity of the response and must check it by some other means. - -For security reasons, only HTTPS connections are allowed (URLs must start with `https://`). The IC uses industry-standard root CA lists to validate certificates of remote web servers. - -The **size** of an HTTP request from the canister or an HTTP response from the remote HTTP server is the total number of bytes representing the names and values of HTTP headers and the HTTP body. The maximal size for the request from the canister is `2MB` (`2,000,000B`). Each request can specify a maximal size for the response from the remote HTTP server. The upper limit on the maximal size for the response is `2MB` (`2,000,000B`) and this value also applies if no maximal size value is specified. An error will be returned when the request or response is larger than the maximal size. - -The following parameters should be supplied for the call: - -- `url` - the requested URL. The URL must be valid according to [RFC-3986](https://www.ietf.org/rfc/rfc3986.txt), it might contain non-ASCII characters according to [RFC-3987](https://www.ietf.org/rfc/rfc3987.txt), and its length must not exceed `8192`. The URL may specify a custom port number. - -- `max_response_bytes` - optional, specifies the maximal size of the response in bytes. If provided, the value must not exceed `2MB` (`2,000,000B`). The call will be charged based on this parameter. If not provided, the maximum of `2MB` will be used. - -- `method` - currently, `GET`, `HEAD`, and `POST` are supported. Additionally, `PUT` and `DELETE` are supported in non-replicated mode only. - -- `headers` - list of HTTP request headers and their corresponding values - -- `body` - optional, the content of the request's body - -- `transform` - an optional record that includes a function that transforms raw responses to sanitized responses, and a byte-encoded context that is provided to the function upon invocation, along with the response to be sanitized. If provided, the calling canister itself must export this function - -- `is_replicated` - optional, selecting between replicated and non-replicated modes. - -:::note - -The `is_replicated` field is considered EXPERIMENTAL. - -::: - -Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). - -The returned response (and the response provided to the `transform` function, if specified) contains the following fields: - -- `status` - the response status (e.g., 200, 404) - -- `headers` - list of HTTP response headers and their corresponding values - -- `body` - the response's body - -The `transform` function may, for example, transform the body in any way, add or remove headers, modify headers, etc. The maximal number of bytes representing the response produced by the `transform` function is equal to `max_response_bytes`, if provided, otherwise the default value of `2MB` (`2,000,000B`) is used as the limit. Note that the number of bytes representing the response produced by the `transform` function includes the serialization overhead of the encoding produced by the canister. - -When the transform function is invoked by the system due to a canister HTTP request, the caller's identity is the principal of the management canister. This information can be used by developers to implement an access control mechanism for this function. - -The following additional limits apply to HTTP requests and HTTP responses from the remote server: - -- the number of headers must not exceed `64`, - -- the number of bytes representing a header name or value must not exceed `8KiB`, and - -- the total number of bytes representing the header names and values must not exceed `48KiB`. - -If the request headers provided by the canister do not contain a `user-agent` header (case-insensitive), -then the IC sends a `user-agent` header (case-insensitive) with the value `ic/1.0` -in addition to the headers provided by the canister. Such an additional header does not contribute -to the above limits on HTTP request headers. - -:::note - -The Internet Computer mainnet supports requests to both IPv6 and IPv4 destinations. The system prioritizes a direct connection to IPv6 addresses (i.e., the domain has a `AAAA` DNS record). If a direct connection cannot be established (e.g., the domain only has an IPv4 address via an A record), the request is automatically retried through a proxy. - -::: - -:::warning - -If you do not specify the `max_response_bytes` parameter, the maximum of a `2MB` response will be charged for, which is expensive in terms of cycles. Always set the parameter to a reasonable upper bound of the expected (network and transformed) response size to not incur unnecessary cycles costs for your request. - -::: - -### IC method `node_metrics_history` {#ic-node_metrics_history} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -:::note - -The node metrics management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. - -::: - -Given a subnet ID as input, this method returns a time series of node metrics (field `node_metrics`). The timestamps are represented as nanoseconds since 1970-01-01 (field `timestamp_nanos`) at which the metrics were sampled. The returned timestamps are all timestamps after (and including) the provided timestamp (field `start_at_timestamp_nanos`) for which node metrics are available. The maximum number of returned timestamps is 60 and no two returned timestamps belong to the same UTC day. - -Note that a sample will only include metrics for nodes whose metrics changed compared to the previous sample. This means that if a node disappears in one sample and later reappears its metrics will restart from 0 and consumers of this API need to adjust for these resets when aggregating over multiple samples. - -A single metric entry is a record with the following fields: - -- `node_id` (`principal`): the principal characterizing a node; - -- `num_blocks_proposed_total` (`nat64`): the number of blocks proposed by this node; - -- `num_block_failures_total` (`nat64`): the number of failed block proposals by this node. - -### IC method `subnet_info` {#ic-subnet_info} - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -Given a subnet ID as input, this method returns a record `subnet_info` containing metadata about that subnet. - -The fields returned are: -- `replica_version` (`text`) of the targeted subnet -- `registry_version` (`nat64`) of the targeted subnet - -### 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. - -A `take_canister_snapshot` call creates a new snapshot. However, the call might fail if the maximum number of snapshots per canister is reached. This error can be avoided by providing an existing snapshot ID via the optional `replace_snapshot` parameter. That existing snapshot will be deleted once a new snapshot has been successfully created. - -It's important to note that a new snapshot might increase the memory footprint of the canister. Thus, the canister's balance must have a sufficient amount of cycles so that the canister does not become frozen. This issue can be mitigated by uninstalling code of the canister via the optional `uninstall_code` parameter after a new snapshot has been successfully created. The exact semantics of uninstalling code is described in the section on the IC method [`uninstall_code`](#ic-uninstall_code). - -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. - -::: - -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 `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. - -The snapshot can only be loaded onto a canister that belongs to the same subnet as the canister from which the snapshot is loaded. - -The caller must be a controller of - -- the canister onto which the snapshot is loaded; and - -- the canister from which the snapshot is loaded. - -:::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 `read_canister_snapshot_metadata` {#ic-read_canister_snapshot_metadata} - -This method can be called by canisters as well as by external users via ingress messages. - -Who is allowed to read the metadata of a snapshot of that canister is determined by the field `snapshot_visibility` in `canister_settings` and can be one of the following variants: -- `controllers`: Only the canister's controllers can read its snapshots metadata -- `public`: Anyone can read the canister's snapshots metadata -- `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can read snapshots metadata, the maximum length of the list is 10 - -This method returns all metadata of a snapshot identified by `snapshot_id` of the canister identified by `canister_id`. It fails if no snapshot with the specified `snapshot_id` can be found for that canister. - -The returned metadata of a snapshot contain: - -- the "source" of the snapshot, i.e., whether the snapshot was created by taking the canister state using the method [`take_canister_snapshot`](#ic-take_canister_snapshot) or by (snapshot) metadata upload using the method [`upload_canister_snapshot_metadata`](#ic_upload_canister_snapshot_metadata); - -- the timestamp at which the snapshot was created, i.e., the method [`take_canister_snapshot`](#ic-take_canister_snapshot) or [`upload_canister_snapshot_metadata`](#ic_upload_canister_snapshot_metadata) executed; - -- the size of the canister WASM (in bytes); - -- values of WASM globals (not to be confused with global variables in a high-level programming language) that are either exported or mutable in the canister WASM; - -- sizes of WASM (a.k.a. heap) and stable memory (in bytes); - -- hashes of chunks in the WASM chunk store; - -- the [canister version](#system-api-canister-version) when the snapshot was created, i.e., the method [`take_canister_snapshot`](#ic-take_canister_snapshot) or [`upload_canister_snapshot_metadata`](#ic_upload_canister_snapshot_metadata) executed; - -- the [certified data](#system-api-certified-data); - -- (optional) the state of the [global timer](#global-timer), i.e., whether it is inactive or active with a deadline (in nanoseconds since 1970-01-01); - -- (optional) the state of the [on low wasm memory](#on-low-wasm-memory) hook, i.e., whether the condition for the hook to be scheduled is not satisfied, the hook is ready to be executed (i.e., the hook has been scheduled), or the hook has already been executed. - -The state of the global timer and on low wasm memory hook are `null` for existing snapshots created before release [release-2025-04-03_03-15-base (68fc31a141b25f842f078c600168d8211339f422](https://dashboard.internetcomputer.org/release/68fc31a141b25f842f078c600168d8211339f422) rolled out between April 7, 2025, and April 14, 2025, in the ICP mainnet. - -### IC method `read_canister_snapshot_data` {#ic-read_canister_snapshot_data} - -This method can be called by canisters as well as by external users via ingress messages. - -Who is allowed to read a snapshot of that canister is determined by the field `snapshot_visibility` in `canister_settings` and can be one of the following variants: -- `controllers`: Only the canister's controllers can read its snapshots -- `public`: Anyone can read the canister's snapshots -- `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can read snapshots, the maximum length of the list is 10 - -This method returns a requested kind of (binary) data from a snapshot identified by `snapshot_id` of the canister identified by `canister_id`. It fails if no snapshot with the specified `snapshot_id` can be found for that canister. - -The following kinds of (binary) data from a snapshot can be requested: - -- chunk of the canister WASM starting at a given `offset` and with a given `size` of the chunk (`offset + size` must not exceed the canister WASM size as in the snapshot metadata); - -- chunk of the WASM (a.k.a. heap) memory starting at a given `offset` and with a given `size` of the chunk (`offset + size` must not exceed the WASM memory size as in the snapshot metadata); - -- chunk of the stable memory starting at a given `offset` and with a given `size` of the chunk (`offset + size` must not exceed the stable memory size as in the snapshot metadata); - -- (full) chunk in the WASM chunk store identified by its `hash` (`hash` must be present in the snapshot metadata). - -### IC method `upload_canister_snapshot_metadata` {#ic-upload_canister_snapshot_metadata} - -This method can be called by canisters as well as by external users via ingress messages. - -Only controllers of a canister can create a snapshot of that canister by uploading the snapshot's metadata. - -An `upload_canister_snapshot_metadata` call creates a new snapshot. However, the call might fail if the maximum number of snapshots per canister is reached. This error can be avoided by providing an existing snapshot ID via the optional `replace_snapshot` parameter. That existing snapshot will be deleted once a new snapshot has been successfully created (in particular, before data is uploaded to that new snapshot using subsequent `upload_canister_snapshot_data` calls). - -It's important to note that a new snapshot will increase the memory footprint of the canister. Thus, the canister's balance must have a sufficient amount of cycles so that the canister does not become frozen. - -Uploaded metadata of a snapshot contain: - -- the size of the canister WASM (in bytes); - -- values of WASM globals (not to be confused with global variables in a high-level programming language) that are either exported or mutable in the canister WASM; - -- sizes of WASM (a.k.a. heap) and stable memory (in bytes); - -- the [certified data](#system-api-certified-data); - -- (optional) the state of the [global timer](#global-timer), i.e., whether it is inactive or active with a deadline (in nanoseconds since 1970-01-01); - -- (optional) the state of the [on low wasm memory](#on-low-wasm-memory) hook, i.e., whether the condition for the hook to be scheduled is not satisfied, the hook is ready to be executed (i.e., the hook has been scheduled), or the hook has already been executed. - -If the state of the global timer and/or the on low wasm memory hook are not provided in the uploaded metadata, -then their state is not updated when loading the snapshot using the method `load_canister_snapshot`. - -### IC method `upload_canister_snapshot_data` {#ic-upload_canister_snapshot_data} - -This method can be called by canisters as well as by external users via ingress messages. - -Only controllers of a canister can upload data to a snapshot of that canister. - -This method uploads a provided (binary) chunk of a provided kind of (binary) data to a snapshot identified by `snapshot_id` of the canister identified by `canister_id`. It fails if no snapshot with the specified `snapshot_id` can be found for that canister or if the snapshot with the specified `snapshot_id` has been created using the method `take_canister_snapshot` (i.e., not by uploading snapshot metadata). - -The following kinds of (binary) data can be uploaded to a snapshot: - -- chunk of the canister WASM starting at a given `offset` (`offset + |chunk|` must not exceed the canister WASM size as in the snapshot metadata); - -- chunk of the WASM (a.k.a. heap) memory starting at a given `offset` (`offset + |chunk|` must not exceed the WASM memory size as in the snapshot metadata); - -- chunk of the stable memory starting at a given `offset` (`offset + |chunk|` must not exceed the stable memory size as in the snapshot metadata); - -- (full) chunk in the WASM chunk store (the length `|chunk|` of the provided chunk must be at most 1MiB and the maximum number of chunks in the chunk store of the snapshot is `CHUNK_STORE_SIZE` chunks). - -It's important to note that uploading a chunk to the WASM chunk store of the snapshot will increase the memory footprint of the canister. Thus, the canister's balance must have a sufficient amount of cycles so that the canister does not become frozen. On the other hand, uploading a chunk to the canister WASM, WASM (a.k.a.) heap memory, and stable memory does increase the memory footprint of the canister since their sizes have been fixed when uploading the snapshot's metadata. - -### 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`. - -Who is allowed to list the snapshots of that canister is determined by the field `snapshot_visibility` in `canister_settings` and can be one of the following variants: -- `controllers`: Only the canister's controllers can list its snapshots -- `public`: Anyone can list the canister's snapshots -- `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can list the snapshots, the maximum length of the list is 10 - -### 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` or `upload_canister_snapshot_metadata` 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 that the snapshot belongs to. - -### IC method `fetch_canister_logs` {#ic-fetch_canister_logs} - -This method can only be called by external users via non-replicated (query) calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls. - -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, TQ, 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` and can be one of the following variants: - -- `controllers`: only the canister's controllers can fetch logs (default); -- `public`: everyone can fetch logs; -- `allowed_viewers` (`vec principal`): only principals in the provided list and the canister's controllers can fetch logs, the maximum length of the list is 10. - -A single log is a record with the following fields: - -- `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; - -:::warning - -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. - -::: - -### IC method `list_canisters` {#ic-list_canisters} - -This method can only be called by external users with subnet admin privileges via non-replicated (query) calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls. - -This method returns the list of all canisters on the subnet as consecutive canister ID ranges. Deleted canisters are not included in the result. - -A canister ID range is a record with the following fields: - -- `start` (`principal`): the first canister ID in the range (inclusive); -- `end` (`principal`): the last canister ID in the range (inclusive). - -:::warning - -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 IC Bitcoin API {#ic-bitcoin-api} - -The Bitcoin API exposed by the management canister is DEPRECATED. -Developers should interact with the Bitcoin canisters (`ghsi2-tqaaa-aaaan-aaaca-cai` for Bitcoin mainnet and `g4xu7-jiaaa-aaaan-aaaaq-cai` for Bitcoin testnet) directly. -Information about Bitcoin and the IC Bitcoin integration can be found in the [Bitcoin developer guides](https://developer.bitcoin.org/devguide/) and the [Bitcoin integration documentation](../guides/chain-fusion/bitcoin.mdx). - -### IC method `bitcoin_get_utxos` {#ic-bitcoin_get_utxos} - -:::note - -This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. - -::: - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -Given a `get_utxos_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns all unspent transaction outputs (UTXOs) associated with the provided address in the specified Bitcoin network based on the current view of the Bitcoin blockchain available to the Bitcoin component. The UTXOs are returned sorted by block height in descending order. - -The following address formats are supported: - -- Pay to public key hash (P2PKH) - -- Pay to script hash (P2SH) - -- Pay to witness public key hash (P2WPKH) - -- Pay to witness script hash (P2WSH) - -- Pay to taproot (P2TR) - -If the address is malformed, the call is rejected. - -The optional `filter` parameter can be used to restrict the set of returned UTXOs, either providing a minimum number of confirmations or a page reference when pagination is used for addresses with many UTXOs. In the first case, only UTXOs with at least the provided number of confirmations are returned, i.e., transactions with fewer than this number of confirmations are not considered. In other words, if the number of confirmations is `c`, an output is returned if it occurred in a transaction with at least `c` confirmations and there is no transaction that spends the same output with at least `c` confirmations. - -There is an upper bound of 144 on the minimum number of confirmations. If a larger minimum number of confirmations is specified, the call is rejected. Note that this is not a severe restriction as the minimum number of confirmations is typically set to a value around 6 in practice. - -It is important to note that the validity of transactions is not verified in the Bitcoin component. The Bitcoin component relies on the proof of work that goes into the blocks and the verification of the blocks in the Bitcoin network. For a newly discovered block, a regular Bitcoin (full) node therefore provides a higher level of security than the Bitcoin component, which implies that it is advisable to set the number of confirmations to a reasonably large value, such as 6, to gain confidence in the correctness of the returned UTXOs. - -There is an upper bound of 10,000 UTXOs that can be returned in a single request. For addresses that contain sufficiently many UTXOs, a partial set of the address's UTXOs are returned along with a page reference. - -In the second case, a page reference (a series of bytes) must be provided, which instructs the Bitcoin component to collect UTXOs starting from the corresponding "page". - -A `get_utxos_request` without the optional `filter` results in a request that considers the full blockchain, which is equivalent to setting `min_confirmations` to 0. - -The recommended workflow is to issue a request with the desired number of confirmations. If the `next_page` field in the response is not empty, there are more UTXOs than in the returned vector. In that case, the `page` field should be set to the `next_page` bytes in the subsequent request to obtain the next batch of UTXOs. - -### IC method `bitcoin_get_balance` {#ic-bitcoin_get_balance} - -:::note - -This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. - -::: - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -Given a `get_balance_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns the current balance of this address in `Satoshi` (10^8 Satoshi = 1 Bitcoin) in the specified Bitcoin network. The same address formats as for [`bitcoin_get_utxos`](#ic-bitcoin_get_utxos) are supported. - -If the address is malformed, the call is rejected. - -The optional `min_confirmations` parameter can be used to limit the set of considered UTXOs for the calculation of the balance to those with at least the provided number of confirmations in the same manner as for the [`bitcoin_get_utxos`](#ic-bitcoin_get_utxos) call. - -Given an address and the optional `min_confirmations` parameter, `bitcoin_get_balance` iterates over all UTXOs, i.e., the same balance is returned as when calling [`bitcoin_get_utxos`](#ic-bitcoin_get_utxos) for the same address and the same number of confirmations and, if necessary, using pagination to get all UTXOs for the same tip hash. - -### IC method `bitcoin_send_transaction` {#ic-bitcoin_send_transaction} - -:::note - -This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. - -::: - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -Given a `send_transaction_request`, which must specify a `blob` of a Bitcoin transaction and a Bitcoin network (`mainnet` or `testnet`), several checks are performed: - -- The transaction is well formed. - -- The transaction only consumes unspent outputs with respect to the current (longest) blockchain, i.e., there is no block on the (longest) chain that consumes any of these outputs. - -- There is a positive transaction fee. - -If at least one of these checks fails, the call is rejected. - -If the transaction passes these tests, the transaction is forwarded to the specified Bitcoin network. Note that the function does not provide any guarantees that the transaction will make it into the mempool or that the transaction will ever appear in a block. - -### IC method `bitcoin_get_current_fee_percentiles` {#ic-bitcoin_get_current_fee_percentiles} - -:::note - -This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. - -::: - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -The transaction fees in the Bitcoin network change dynamically based on the number of pending transactions. It must be possible for a canister to determine an adequate fee when creating a Bitcoin transaction. - -This function returns fee percentiles, measured in millisatoshi/vbyte (1000 millisatoshi = 1 satoshi), over the last 10,000 transactions in the specified network, i.e., over the transactions in the last approximately 4-10 blocks. - -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 `bitcoin_get_block_headers` {#ic-bitcoin_get_block_headers} - -:::note - -This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. - -::: - -This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. - -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. - -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. - -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 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} - -Some parts of the IC state are exposed to users in a tamperproof way via certification: the IC can reveal a *partial state tree* which includes just the data of interest, together with a signature on the root hash of the state tree. This means that a user can be sure that the response is correct, even if the user happens to be communicating with a malicious node, or has received the certificate via some other untrusted way. - -To validate a value using a certificate, the user conceptually - -1. checks the validity of the partial tree using `verify_cert`, - -2. looks up the value in the certificate using `lookup` at a given path, which uses the subroutine `lookup_path` on the certificate's tree. - -This mechanism is used in the `read_state` request type, and eventually also for other purposes. - -### Root of trust - -The root of trust is the *root public key*, which must be known to the user a priori. In a local canister execution environment, the key can be fetched via the [`/api/v2/status`](#api-status) endpoint. - -### Certificate - -A certificate consists of - -- a tree - -- a signature on the tree root hash valid under some *public key* - -- an optional *delegation* that links that public key to *root public key*. - -The IC will certify states by issuing certificates where the tree is a partial state tree. The state tree can be pruned by replacing subtrees with their root hashes (yielding a new and potentially smaller but still valid certificate) to only include paths pertaining to relevant data but still preserving enough information to recover the *tree root hash*. - -More formally, a certificate is described by the following data structure: -``` -Certificate = { - tree : HashTree - signature : Signature - delegation : NoDelegation | Delegation -} -HashTree - = Empty - | Fork HashTree HashTree - | Labeled Label HashTree - | Leaf blob - | Pruned Hash -Label = Blob -Hash = Blob -Signature = Blob -``` - -A certificate is validated with regard to the root of trust by the following algorithm (which uses `check_delegation` defined in [Delegation](#certification-delegation)): - - verify_cert(cert) = - let root_hash = reconstruct(cert.tree) - // see section Delegations below - if check_delegation(cert.delegation) = false then return false - let bls_key = delegation_key(cert.delegation) - verify_bls_signature(bls_key, cert.signature, domain_sep("ic-state-root") · root_hash) - - reconstruct(Empty) = H(domain_sep("ic-hashtree-empty")) - reconstruct(Fork t1 t2) = H(domain_sep("ic-hashtree-fork") · reconstruct(t1) · reconstruct(t2)) - reconstruct(Labeled l t) = H(domain_sep("ic-hashtree-labeled") · l · reconstruct(t)) - reconstruct(Leaf v) = H(domain_sep("ic-hashtree-leaf") · v) - reconstruct(Pruned h) = h - - domain_sep(s) = byte(|s|) · s - -where `H` is the SHA-256 hash function, - - verify_bls_signature : PublicKey -> Signature -> Blob -> Bool - -is the [BLS signature verification function](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-4), ciphersuite BLS\_SIG\_BLS12381G1\_XMD:SHA-256\_SSWU\_RO\_NUL\_. See that document also for details on the encoding of BLS public keys and signatures. - -All state trees include the time at path `/time` (see [Time](#state-tree-time)). Users that get a certificate with a state tree can look up the timestamp to guard against working on obsolete data. - -### Lookup {#lookup} - -Given a (verified) tree, the user can fetch the value at a given path, which is a sequence of labels (blobs). In this document, we write paths suggestively with slashes as separators; the actual encoding is not actually using slashes as delimiters. - -The following algorithm looks up a `path` in a certificate, and returns either - -- `Found v`: the requested `path` has an associated value `v` in the tree, - -- `Absent`: the requested path is not in the tree, - -- `Unknown`: it cannot be syntactically determined if the requested `path` was pruned or not; i.e., there exist at least two trees (one containing the requested path and one *not* containing the requested path) from which the given tree can be obtained by pruning some subtrees, - -- `Error`: the requested path does not have an associated value in the tree, but the requested path is in the tree: - -```html - -lookup(path, cert) = lookup_path(path, cert.tree) - -lookup_path([], Empty) = Absent -lookup_path([], Leaf v) = Found v -lookup_path([], Pruned _) = Unknown -lookup_path([], Labeled _ _) = Error -lookup_path([], Fork _ _) = Error - -lookup_path(l::ls, tree) = - match find_label(l, flatten_forks(tree)) with - | Absent -> Absent - | Unknown -> Unknown - | Error -> Error - | Found subtree -> lookup_path ls subtree - -flatten_forks(Empty) = [] -flatten_forks(Fork t1 t2) = flatten_forks(t1) · flatten_forks(t2) -flatten_forks(t) = [t] - -find_label(l, _ · Labeled l1 t · _) | l == l1 = Found t -find_label(l, _ · Labeled l1 _ · Labeled l2 _ · _) | l1 < l < l2 = Absent -find_label(l, Labeled l2 _ · _) | l < l2 = Absent -find_label(l, _ · Labeled l1 _ ) | l1 < l = Absent -find_label(l, [Leaf _]) = Absent -find_label(l, []) = Absent -find_label(l, _) = Unknown - -``` - -Given a path `prefix`, we define `lookup*(prefix, cert)` to be the concatenation of all values at paths with the given prefix, -i.e., for every `path` of the form `path = prefix · _` with `lookup(path, cert) = Found v`. - -The IC will only produce well-formed state trees, and the above algorithm assumes well-formed trees. These have the property that labeled subtrees appear in strictly increasing order of labels, and are not mixed with leaves. More formally: - - well_formed(tree) = - (tree = Leaf _) ∨ (well_formed_forest(flatten_forks(tree))) - - well_formed_forest(trees) = - strictly_increasing([l | Label l _ ∈ trees]) ∧ - ∀ Label _ t ∈ trees. well_formed(t) ∧ - ∀ t ∈ trees. t ≠ Leaf _ - -### Delegation {#certification-delegation} - -The root key can delegate certification authority to other keys. - -A certificate by the root subnet does not have a delegation field. A certificate by other subnets include a delegation, which is itself a certificate that proves that the subnet is listed in the root subnet's state tree (see [Subnet information](#state-tree-subnet)), and reveals its public key. - -:::note - -The certificate included in the delegation (if present) must not itself again contain a delegation. - -::: - -``` -Delegation = { - subnet_id : Principal; - certificate : Certificate; - } -``` - -A delegation is verified using the following algorithm: -``` -check_delegation(NoDelegation) = true -check_delegation(Delegation d) = verify_cert(d.certificate) and lookup(["subnet",d.subnet_id,"public_key"],d.certificate) = Found _ and d.certificate.delegation = NoDelegation -``` - -The delegation key (a BLS key) is computed by the following algorithm: -``` -delegation_key(NoDelegation) : public_bls_key = root_public_key -delegation_key(Delegation d) : public_bls_key = - match lookup(["subnet",d.subnet_id,"public_key"],d.certificate) with - Found der_key -> extract_der(der_key) -``` - -where `root_public_key` is the a priori known root key and -``` -extract_der : Blob -> Blob -``` - -implements DER decoding of the public key, following [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) using OID 1.3.6.1.4.1.44668.5.3.1.2.1 for the algorithm and 1.3.6.1.4.1.44668.5.3.2.1 for the curve. - -Delegations are *scoped*, i.e., they indicate which set of canister principals the delegatee subnet may certify for. This set can be obtained from a delegation `d` using `lookup*(["canister_ranges",d.subnet_id],d.certificate)`. See [lookup](#lookup) for the definition of `lookup*` and [Canister ranges](#state-tree-canister-ranges) for the description of the encoding used. The various applications of certificates describe if and how the subnet scope comes into play. - -### Encoding of certificates {#certification-encoding} - -The binary encoding of a certificate is a CBOR (see [CBOR](#cbor)) value according to the following CDDL (see [CDDL](#cddl)). You can also [download the file](_attachments/certificates.cddl). - -The values in the [The system state tree](#state-tree) are encoded to blobs as follows: - -- natural numbers are leb128-encoded. - -- text values are UTF-8-encoded - -- blob values are encoded as is - -### Example - -Consider the following tree-shaped data (all single character strings denote labels, all other denote values) - - ─┬╴ "a" ─┬─ "x" ─╴"hello" - │ └╴ "y" ─╴"world" - ├╴ "b" ──╴ "good" - ├╴ "c" - └╴ "d" ──╴ "morning" - -A possible hash tree for this labeled tree might be, where `┬` denotes a fork. This is not a typical encoding (a fork with `Empty` on one side can be avoided), but it is valid. - - ─┬─┬╴"a" ─┬─┬╴"x" ─╴"hello" - │ │ │ └╴Empty - │ │ └╴ "y" ─╴"world" - │ └╴"b" ──╴"good" - └─┬╴"c" ──╴Empty - └╴"d" ──╴"morning" - -This tree has the following CBOR (see [CBOR](#cbor)) encoding - - 8301830183024161830183018302417882034568656c6c6f810083024179820345776f726c6483024162820344676f6f648301830241638100830241648203476d6f726e696e67 - -and the following root hash - - eb5c5b2195e62d996b84c9bcc8259d19a83786a2f59e0878cec84c811f669aa0 - -Pruning this tree with the following paths - - /a/y - /ax - /d - -would lead to this tree (with pruned subtree represented by their hash): - - ─┬─┬╴"a" ─┬─ 1B4FEFF9BEF8131788B0C9DC6DBAD6E81E524249C879E9F10F71CE3749F5A638 - │ │ └╴ "y" ─╴"world" - │ └╴"b" ──╴7B32AC0C6BA8CE35AC82C255FC7906F7FC130DAB2A090F80FE12F9C2CAE83BA6 - └─┬╴EC8324B8A1F1AC16BD2E806EDBA78006479C9877FED4EB464A25485465AF601D - └╴"d" ──╴"morning" - -Note that the `"b"` label is included (without content) to prove the absence of the `/ax` path. - -This tree encodes to CBOR as - - 83018301830241618301820458201b4feff9bef8131788b0c9dc6dbad6e81e524249c879e9f10f71ce3749f5a63883024179820345776f726c6483024162820458207b32ac0c6ba8ce35ac82c255fc7906f7fc130dab2a090f80fe12f9c2cae83ba6830182045820ec8324b8a1f1ac16bd2e806edba78006479c9877fed4eb464a25485465af601d830241648203476d6f726e696e67 - -and (obviously) the same root hash. - -In the pruned tree, the `lookup_path` function behaves as follows: - - lookup_path(["a", "a"], pruned_tree) = Unknown - lookup_path(["a", "y"], pruned_tree) = Found "world" - lookup_path(["aa"], pruned_tree) = Absent - lookup_path(["ax"], pruned_tree) = Absent - lookup_path(["b"], pruned_tree) = Unknown - lookup_path(["bb"], pruned_tree) = Unknown - lookup_path(["d"], pruned_tree) = Found "morning" - lookup_path(["e"], pruned_tree) = Absent - -## The HTTP Gateway protocol {#http-gateway} - -The HTTP Gateway Protocol has been moved into its own [specification](./http-gateway-spec.md). - -## Abstract behavior {#abstract-behavior} - -The previous sections describe the interfaces, i.e. outer edges of the Internet Computer, but give only intuitive and vague information in prose about what these interfaces actually do. - -The present section aims to address that question with great precision, by describing the *abstract state* of the whole Internet Computer, and how this state can change in response to API function calls, or spontaneously (modeling asynchronous, distributed or non-deterministic execution). - -The design of this abstract specification (e.g. how and where pending messages are stored) are *not* to be understood to in any way prescribe a concrete implementation or software architecture. The goals here are formal precision and clarity, but not implementability, so this can lead to different ways of phrasing. - -### Notation - -We specify the behavior of the Internet Computer using ad-hoc pseudocode. - -The manipulated values are primitive values (numbers, text, binary blobs), aggregate values (lists, unordered lists a.k.a. bags, partial maps, records with fixed fields, named constructors) and functions. - -We use a concatenation operator `·` with various types: to extend sets and maps, or to concatenate lists with lists or lists with elements. - -The shape of values is described using a hand-wavy type system. We use `Foo = Nat` to define type aliases; now `Foo` can be used instead of `Nat`. Often, the right-hand side is a more complex type here, e.g. a record, or multiple possible types separated by a vertical bar (`|`). Partial maps are written as `Key ↦ Value` and the function type as `Argument → Result`. - -:::note - -All values are immutable! State change is specified by describing the new state, not by changing the existing state. - -::: - -Record fields are accessed using dot-notation (e.g. `S.request_id > 0`). To create a new record from an existing record `R` with some fields changed, the syntax `R where field = new_value` is used. This syntax can also be used to create new records with some deeply nested field changed: `R where some_map[key].field = new_value`. - -In the state transitions, upper-case variables (`S`, `C`, `Req_id`) are free variables: The state transition may be taken for any possible value of these variables. `S` always refers to the previous state. A state transition often comes with a list of *conditions*, which may restrict the values of these free variables. The *state after* is usually described using the record update syntax by starting with `S where`. - -For example, the condition `S.messages = Older_messages · M · Younger_messages` says that `M` is some message in field `messages` of the record `S`, and that `Younger_messages` and `Older_messages` are the other messages in the state. If the "state after" specifies `S with messages = Older_messages · Younger_messages`, then the message `M` is removed from the state. - -### Abstract state - -In this specification, we describe the Internet Computer as a state machine. In particular, there is a single piece of data that describes the complete state of the IC, called `S`. - -Of course, this is a huge simplification: The real Internet Computer is distributed and has a multi-component architecture, and the state is spread over many different components, some physically separated. But this simplification allows us to have a concise description of the behavior, and to easily make global decisions (such as, "is there any pending message"), without having to specify the bookkeeping that allows such global decisions. - -#### Identifiers - -Principals (canister ids and user ids) are blobs, but some of them have special form, as explained in [Special forms of Principals](#id-classes). -``` -type Principal = Blob -``` -The function -``` -mk_self_authenticating_id : PublicKey -> Principal -mk_self_authenticating_id pk = H(pk) · 0x02 -``` -calculates self-authenticating ids. - -The function -``` -canister_signature_pk : Principal -> Blob -> PublicKey -``` -calculates the public key of a [canister signature](#canister-signatures). - -The function -``` -mk_derived_id : Principal -> Blob -> Principal -mk_derived_id p nonce = H(|p| · p · nonce) · 0x03 -``` -calculates derived ids. With `|p|` we denote the length of the principal, in bytes, encoded as a single byte. - -The principal of the anonymous user is fixed: -``` -anonymous_id : Principal -anonymous_id = 0x04 -``` -The principal of the management canister is the empty blob (i.e. `aaaaa-aa`): -``` -ic_principal : Principal = "" -``` -These function domains and fixed values are mutually disjoint. - -Method names can be arbitrary pieces of text: -``` -MethodName = Text -``` -#### Abstract canisters {#abstract-canisters} - -The [WebAssembly System API](#system-api) is relatively low-level, and some of its details (e.g. that the argument data is queried using separate calls, and that closures are represented by a function pointer and a number, that method names need to be mangled) would clutter this section. Therefore, we abstract over the WebAssembly details as follows: - -- The state `WasmState` of a WebAssembly module is represented by its WASM (a.k.a. heap) and stable memory and a list of (exported or mutable) globals. For notational simplicity, the principal of the canister with state represented by `WasmState` is also stored in `WasmState`. - -- A canister module `CanisterModule` consists of an initial state, and (pure) functions that model function invocation on the canister. A function return value either indicates that the canister function traps, or returns a new state together with a description of the invoked asynchronous System API calls. - ``` - WasmState = { - wasm_memory : Blob; - stable_memory : Blob; - globals : [Global]; - self_id : Principal; - } - Global - = I32(Int) - | I64(Int) - | F32(Real) - | F64(Real) - | V128(Nat); - Callback = (abstract) - ChunkStore = Hash -> Blob - - Arg = Blob; - CallerId = Principal; - CallerInfoData = Blob; - CallerInfoSigner = Blob; - - Timestamp = Nat; - CanisterVersion = Nat; - Env = { - time : Timestamp; - controllers : List Principal; - global_timer : Nat; - balance : Nat; - reserved_balance : Nat; - reserved_balance_limit : Nat; - compute_allocation : Nat; - memory_allocation : Nat; - memory_usage_raw_module : Nat; - memory_usage_canister_history : Nat; - memory_usage_chunk_store : Nat; - memory_usage_snapshots : Nat; - freezing_threshold : Nat; - subnet_id : Principal; - subnet_size : Nat; - certificate : NoCertificate | Blob; - status : Running | Stopping | Stopped; - canister_version : CanisterVersion; - } - - RejectCode = Nat - Response = Reply Blob | Reject (RejectCode, Text) - MethodCall = { - callee : CanisterId; - method_name: MethodName; - arg: Blob; - transferred_cycles: Nat; - callback: Callback; - timeout_seconds : NoTimeout | Nat; - } - - UpdateFunc = WasmState -> Trap { cycles_used : Nat; } | Return { - new_state : WasmState; - new_calls : List MethodCall; - new_certified_data : NoCertifiedData | Blob; - new_global_timer : NoGlobalTimer | Nat; - response : NoResponse | Response; - cycles_accepted : Nat; - cycles_used : Nat; - } - QueryFunc = WasmState -> Trap { cycles_used : Nat; } | Return { - response : Response; - cycles_accepted : Nat; - cycles_used : Nat; - } - CompositeQueryFunc = WasmState -> Trap { cycles_used : Nat; } | Return { - new_state : WasmState; - new_calls : List MethodCall; - response : NoResponse | Response; - cycles_used : Nat; - } - SystemTaskFunc = WasmState -> Trap { cycles_used : Nat; } | Return { - new_state : WasmState; - new_calls : List MethodCall; - new_certified_data : NoCertifiedData | Blob; - new_global_timer : NoGlobalTimer | Nat; - cycles_used : Nat; - } - - AvailableCycles = Nat - RefundedCycles = Nat - - CanisterModule = { - initial_globals : [Global]; - init : (CanisterId, Arg, CallerId, Env) -> Trap { cycles_used : Nat; } | Return { - new_state : WasmState; - new_certified_data : NoCertifiedData | Blob; - new_global_timer : NoGlobalTimer | Nat; - cycles_used : Nat; - } - pre_upgrade : (WasmState, Principal, Env) -> Trap { cycles_used : Nat; } | Return { - new_state : WasmState; - new_certified_data : NoCertifiedData | Blob; - cycles_used : Nat; - } - post_upgrade : (WasmState, Arg, CallerId, Env) -> Trap { cycles_used : Nat; } | Return { - new_state : WasmState; - new_certified_data : NoCertifiedData | Blob; - new_global_timer : NoGlobalTimer | Nat; - cycles_used : Nat; - } - update_methods : MethodName ↦ ((Arg, CallerId, CallerInfoData, CallerInfoSigner, Deadline, Env, AvailableCycles) -> UpdateFunc) - query_methods : MethodName ↦ ((Arg, CallerId, CallerInfoData, CallerInfoSigner, Env) -> QueryFunc) - composite_query_methods : MethodName ↦ ((Arg, CallerId, CallerInfoData, CallerInfoSigner, Env) -> CompositeQueryFunc) - heartbeat : (Env) -> SystemTaskFunc - global_timer : (Env) -> SystemTaskFunc - on_low_wasm_memory : (Env) -> SystemTaskFunc - callbacks : (Callback, CallerId, CallerInfoData, CallerInfoSigner, Response, Deadline, RefundedCycles, Env, AvailableCycles) -> UpdateFunc - composite_callbacks : (Callback, CallerId, CallerInfoData, CallerInfoSigner, Response, Env) -> UpdateFunc - inspect_message : (MethodName, WasmState, Arg, CallerId, CallerInfoData, CallerInfoSigner, Env) -> Trap | Return { - status : Accept | Reject; - } - } - ``` - -This high-level interface presents a pure, mathematical model of a canister, and hides the bookkeeping required to provide the System API as seen in Section [Canister interface (System API)](#system-api). - -The `CanisterId` parameter of `init` is merely passed through to the canister, via the `canister.self` system call. - -The `Env` parameter provides synchronous read-only access to portions of the system state and canister metadata that are always available. - -The parsing of a blob to a canister module and its public and private custom sections is modelled via the (possibly implicitly failing) functions -``` -parse_wasm_mod : Blob -> CanisterModule -parse_public_custom_sections : Blob -> Text ↦ Blob -parse_private_custom_sections : Blob -> Text ↦ Blob -``` - -The concrete mapping of this abstract `CanisterModule` to actual WebAssembly concepts and the System API is described separately in section [Abstract Canisters to System API](#concrete-canisters). - -#### Call contexts - -The Internet Computer provides certain messaging guarantees: If a user or a canister calls another canister, it will eventually get a single response (a reply or a rejection), even if some canister code along the way fails. - -To ensure that only one response is generated, and also to detect when no response can be generated any more, the IC maintains a *call context*. The `needs_to_respond` field is set to `false` once the call has received a response. Further attempts to respond will now fail. -``` -Request = { - nonce : Blob; - ingress_expiry : Nat; - sender : UserId; - sender_info : { - info : Blob; - signer : Blob; - sig : Blob; - }; - canister_id : CanisterId; - method_name : Text; - arg : Blob; - } -CallId = (abstract) -CallOrigin - = FromUser { - request : Request; - } - | FromCanister { - calling_context : CallId; - callback: Callback; - deadline : NoDeadline | Timestamp | Expired Timestamp; - } - | FromSystemTask -CallCtxt = { - canister : CanisterId; - origin : CallOrigin; - needs_to_respond : bool; - deleted : bool; - available_cycles : Nat; -} -``` -#### Calls and Messages - -Calls into and within the IC are implemented as messages passed between canisters. During their lifetime, messages change shape: they begin as a call to a public method, which is resolved to a WebAssembly function that is then executed, potentially generating a response which is then delivered. - -Therefore, a message can have different shapes: -``` -Queue = Unordered | Queue { from : System | CanisterId; to : CanisterId } -EntryPoint - = PublicMethod MethodName Principal Blob - | Callback Callback Response RefundedCycles - | Heartbeat - | GlobalTimer - | OnLowWasmMemory -Message - = CallMessage { - origin : CallOrigin; - caller : Principal; - caller_info_data : Blob; - caller_info_signer : Blob; - callee : CanisterId; - method_name : Text; - arg : Blob; - transferred_cycles : Nat; - queue : Queue; - } - | FuncMessage { - call_context : CallId; - caller : Principal; - caller_info_data : Blob; - caller_info_signer : Blob; - receiver : CanisterId; - entry_point : EntryPoint; - queue : Queue; - } - | ResponseMessage { - origin : CallOrigin; - response : Response; - refunded_cycles : Nat; - } -``` - -The `queue` field is used to describe the message ordering behavior. Its concrete value is only used to determine when the relative order of two messages must be preserved, and is otherwise not interpreted. Response messages are not ordered so they have no `queue` field. - -A reference implementation would likely maintain a separate list of `messages` for each such queue to efficiently find eligible messages; this document uses a single global list for a simpler and more concise system state. - -#### API requests - -We distinguish between API requests (type `Request`) passed to `/api/v2/…/call` and `/api/v4/…/call`, which may be present in the IC state, and the *read-only* API requests passed to `/api/v3/…/read_state` and `/api/v3/…/query`, which are only ephemeral. - -These are the read-only messages: -``` -Path = List(Blob) -APIReadRequest - = StateRead = { - nonce : Blob; - ingress_expiry : Nat; - sender : UserId; - paths : List(Path); - } - | CanisterQuery = { - nonce : Blob; - ingress_expiry : Nat; - sender : UserId; - sender_info : { - info : Blob; - signer : Blob; - sig : Blob; - }; - canister_id : CanisterId; - method_name : Text; - arg : Blob; - } -``` - -Signed delegations contain the (unsigned) delegation data in a nested record, next to the signature of that data. -``` -PublicKey = Blob -Signature = Blob -SignedDelegation = { - delegation : { - pubkey : PublicKey; - targets : [CanisterId] | Unrestricted; - expiration : Timestamp - }; - signature : Signature -} -``` - -For the signatures in a `Request`, we assume that the following function implements signature verification as described in [Authentication](#authentication). This function picks the corresponding signature scheme according to the DER-encoded metadata in the public key. -``` -verify_signature : PublicKey -> Signature -> Blob -> Bool -Envelope = { - content : Request | APIReadRequest; - sender_pubkey : PublicKey | NoPublicKey; - sender_sig : Signature | NoSignature; - sender_delegation: [SignedDelegation] -} -``` - -The evolution of a `Request` goes through these states, as explained in [Overview of canister calling](#http-call-overview): -``` -RequestStatus - = Received - | Processing - | Rejected (RejectCode, Text) - | Replied Blob - | Done -``` - -A `Path` may refer to a request by way of a *request id*, as specified in [Request ids](#request-id): -``` -RequestId = { b ∈ Blob | |b| = 32 } -hash_of_map: Request -> RequestId -``` - -#### The system state - -Finally, we can describe the state of the IC as a record having the following fields: -``` -CanState - = EmptyCanister | { - wasm_state : WasmState; - module : CanisterModule; - raw_module : Blob; - public_custom_sections: Text ↦ Blob; - private_custom_sections: Text ↦ Blob; -} -CanStatus - = Running - | Stopping (List (CallOrigin, Nat)) - | Stopped -ChangeOrigin - = FromUser { - user_id : PrincipalId; - } - | FromCanister { - canister_id : PrincipalId; - canister_version : CanisterVersion | NoCanisterVersion; - } -CodeDeploymentMode - = Install - | Reinstall - | Upgrade -SnapshotId = (abstract) -SnapshotSource - = TakenFromCanister - | MetadataUpload -ChangeDetails - = Creation { - controllers : [PrincipalId]; - environment_variables_hash: opt Blob; - } - | CodeUninstall - | CodeDeployment { - mode : CodeDeploymentMode; - module_hash : Blob; - } - | LoadSnapshot { - from_canister_id : PrincipalId; - snapshot_id : SnapshotId; - canister_version : CanisterVersion; - taken_at_timestamp : Timestamp; - source : SnapshotSource; - } - | ControllersChange { - controllers: [PrincipalId]; - } - | RenameCanister { - canister_id : PrincipalId; - total_num_changes : Nat; - rename_to : { - canister_id : PrincipalId; - version : Nat; - total_num_changes : Nat; - }; - requested_by : PrincipalId; - } -Change = { - timestamp_nanos : Timestamp; - canister_version : CanisterVersion; - origin : ChangeOrigin; - details : ChangeDetails; -} -CanisterHistory = { - total_num_changes : Nat; - recent_changes : [Change]; -} -CanisterLogVisibility - = Controllers - | Public - | AllowedViewers [Principal] -CanisterSnapshotVisibility - = Controllers - | Public - | AllowedViewers [Principal] -CanisterLog = { - idx : Nat; - timestamp_nanos : Nat; - content : Blob; -} -OnLowWasmMemoryHookStatus - = ConditionNotSatisfied - | Ready - | Executed -QueryStats = { - timestamp : Timestamp; - num_instructions : Nat; - request_payload_bytes : Nat; - response_payload_bytes : Nat; -} -Subnet = { - subnet_id : Principal; - subnet_size : Nat; -} -Snapshot = { - source : SnapshotSource; - taken_at_timestamp : Timestamp; - raw_module : Blob; - wasm_state : WasmState; - chunk_store : ChunkStore; - canister_version : CanisterVersion; - certified_data : Blob; - global_timer : Timestamp | null; - on_low_wasm_memory_hook_status : OnLowWasmMemoryHookStatus | null; -} -S = { - requests : Request ↦ (RequestStatus, Principal); - canisters : CanisterId ↦ CanState; - snapshots: CanisterId ↦ SnapshotId ↦ Snapshot; - controllers : CanisterId ↦ Set Principal; - compute_allocation : CanisterId ↦ Nat; - memory_allocation : CanisterId ↦ Nat; - freezing_threshold : CanisterId ↦ Nat; - canister_status: CanisterId ↦ CanStatus; - canister_version: CanisterId ↦ CanisterVersion; - canister_subnet : CanisterId ↦ Subnet; - time : CanisterId ↦ Timestamp; - global_timer : CanisterId ↦ Timestamp; - balances: CanisterId ↦ Nat; - reserved_balances: CanisterId ↦ Nat; - reserved_balance_limits: CanisterId ↦ Nat; - wasm_memory_limit: CanisterId ↦ Nat; - wasm_memory_threshold: CanisterId ↦ Nat; - environment_variables: CanisterId ↦ (Text ↦ Text) - on_low_wasm_memory_hook_status: CanisterId ↦ OnLowWasmMemoryHookStatus; - certified_data: CanisterId ↦ Blob; - canister_history: CanisterId ↦ CanisterHistory; - canister_log_visibility: CanisterId ↦ CanisterLogVisibility; - canister_snapshot_visibility: CanisterId ↦ CanisterSnapshotVisibility; - canister_logs: CanisterId ↦ [CanisterLog]; - query_stats: CanisterId ↦ [QueryStats]; - system_time : Timestamp - call_contexts : CallId ↦ CallCtxt; - messages : List Message; // ordered! - root_key : PublicKey -} -``` - -To convert `CanStatus` into `status : Running | Stopping | Stopped` from `Env`, we define the following conversion function: -``` -simple_status(Running) = Running -simple_status(Stopping _) = Stopping -simple_status(Stopped) = Stopped -``` - -To convert `CallOrigin` into `ChangeOrigin`, we define the following conversion function: -``` -change_origin(principal, _, FromUser { … }) = FromUser { - user_id = principal - } -change_origin(principal, sender_canister_version, FromCanister { … }) = FromCanister { - canister_id = principal - canister_version = sender_canister_version - } -change_origin(principal, sender_canister_version, FromSystemTask) = FromCanister { - canister_id = principal - canister_version = sender_canister_version - } -``` - -#### Cycle bookkeeping and resource consumption - -The main cycle balance of canister `A` in state `S` can be obtained with `S.balances(A)`. -In addition to the main balance, each canister has a reserved balance `S.reserved_balances(A)`. -The reserved balance contains cycles that were set aside from the main balance for future payments for the consumption of resources such as compute and memory. -The reserved cycles can only be used for resource payments and cannot be transferred back to the main balance. - -The (unspecified) function `idle_cycles_burned_rate(compute_allocation, memory_allocation, memory_usage, subnet_size)` determines the idle resource consumption rate in cycles per day of a canister given its current compute and memory allocation, memory usage, and subnet size. The function `freezing_limit(compute_allocation, memory_allocation, freezing_threshold, memory_usage, subnet_size)` determines the freezing limit in cycles of a canister given its current compute and memory allocation, freezing threshold in seconds, memory usage, and subnet size. The value `freezing_limit(compute_allocation, memory_allocation, freezing_threshold, memory_usage, subnet_size)` is derived from `idle_cycles_burned_rate(compute_allocation, memory_allocation, memory_usage, subnet_size)` and `freezing_threshold` as follows: -``` -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)`, `memory_usage_canister_history(canister_history)`, `memory_usage_chunk_store(chunk_store)`, and `memory_usage_snapshots(snapshots)` determine the canister's memory usage in bytes consumed by its Wasm state, raw Wasm binary, canister history, chunk store, and snapshots, 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_snapshots(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, 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 = (); - freezing_threshold = (); - canister_status = (); - canister_version = (); - canister_subnet = (); - time = (); - global_timer = (); - balances = (); - reserved_balances = (); - reserved_balance_limits = (); - wasm_memory_limit = (); - wasm_memory_threshold = (); - environment_variables = (); - on_low_wasm_memory_hook_status = (); - certified_data = (); - canister_history = (); - canister_log_visibility = (); - canister_snapshot_visibility = (); - canister_logs = (); - query_stats = (); - system_time = T; - call_contexts = (); - messages = []; - root_key = PublicKey; - subnet_admins = (); -} -``` - -for some time stamp `T`, some DER-encoded BLS public key `PublicKey`, and using `()` to denote the empty map or bag. - -### Invariants - -The following is an incomplete list of invariants that should hold for the abstract state `S`, and are not already covered by the type annotations in this section. - -- No pair of update, query, and composite query methods in a CanisterModule can have the same name: - ``` - ∀ (_ ↦ CanState) ∈ S.canisters: - dom(CanState.module.update_methods) ∩ dom(CanState.module.query_methods) = ∅ - dom(CanState.module.update_methods) ∩ dom(CanState.module.composite_query_methods) = ∅ - dom(CanState.module.query_methods) ∩ dom(CanState.module.composite_query_methods) = ∅ - ``` - -- Deleted call contexts were not awaiting a response: - ``` - ∀ (_ ↦ Ctxt) ∈ S.call_contexts: - if Ctxt.deleted then Ctxt.needs_to_respond = false - ``` -- Responded call contexts have no available\_cycles left: - ``` - ∀ (_ ↦ Ctxt) ∈ S.call_contexts: - if Ctxt.needs_to_respond = false then Ctxt.available_cycles = 0 - ``` -- A stopped canister does not have any call contexts (in particular, a stopped canister does not have any call contexts marked as deleted): - ``` - ∀ (_ ↦ Ctxt) ∈ S.call_contexts: - S.canister_status[Ctxt.canister] ≠ Stopped - ``` -- Referenced call contexts exist, unless the origins have expired deadlines: - - ``` - ∀ CallMessage {origin = FromCanister O, …} ∈ S.messages. O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) - ∀ ResponseMessage {origin = FromCanister O, …} ∈ S.messages. O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) - ∀ (_ ↦ {needs_to_respond = true, origin = FromCanister O, …}) ∈ S.call_contexts: O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) - ∀ (_ ↦ Stopping Origins) ∈ S.canister_status: ∀(FromCanister O, _) ∈ Origins. O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) - ``` -### State transitions - -Based on this abstract notion of the state, we can describe the behavior of the IC. There are three classes of behaviors: - -- Potentially state changing API requests that are submitted via `/api/v2/…/call` and `/api/v4/…/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. - -- Responses to reads (i.e. `/api/v3/…/read_state` and `/api/v3/…/query`). By definition, these do *not* change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. - -The state transitions are not complete with regard to error handling. For example, the behavior of sending a request to a non-existent canister is not specified here. For now, we trust implementors to make sensible decisions there. - -We model the [The IC management canister](#ic-management-canister) with one state transition per method. There, we assume a function -``` -candid : Value -> Blob -``` -that represents Candid encoding; this is implicitly taking the method types, as declared in [Interface overview](#ic-candid), into account. We model the parsing of Candid values in the "Conditions" section using `candid` as well, by treating it as a non-deterministic function. - -#### Envelope Authentication - -The following predicate describes when an envelope `E` correctly signs the enclosed request with a key belonging to a user `U`, at time `T`: It returns which canister ids this envelope may be used at (as a set of principals). -``` -verify_envelope({ content = C }, U, T) - = { p : p is CanisterID } if U = anonymous_id -verify_envelope({ content = C, sender_pubkey = PK, sender_sig = Sig, sender_delegation = DS}, U, T) - = TS if U = mk_self_authenticating_id E.sender_pubkey - ∧ (PK', TS) = verify_delegations(DS, PK, T, { p : p is CanisterId }) - ∧ verify_signature PK' Sig ("\x0Aic-request" · hash_of_map(C)) -verify_delegations([], PK, T, TS) = (PK, TS) -verify_delegations([D] · DS, PK, T, TS) - = verify_delegations(DS, D.pubkey, T, TS ∩ delegation_targets(D)) - if verify_signature PK D.signature ("\x1Aic-request-auth-delegation" · hash_of_map(D.delegation)) - ∧ D.delegation.expiration ≥ T -delegation_targets(D) - = if D.targets = Unrestricted - then { p : p is CanisterId } - else D.targets -``` -#### Effective canister ids - -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 = create_canister, …}, p) -is_effective_canister_id(Request {canister_id = ic_principal, method = provisional_create_canister_with_cycles, …}, p) -is_effective_canister_id(CanisterQuery {canister_id = ic_principal, method = list_canisters, …}, p) -is_effective_canister_id(Request {canister_id = ic_principal, method = install_chunked_code, arg = candid({target_canister = p, …}), …}, p) -is_effective_canister_id(Request {canister_id = ic_principal, arg = candid({canister_id = p, …}), …}, p) -is_effective_canister_id(Request {canister_id = p, …}, p), if p ≠ ic_principal -``` - -#### API Request submission {#api-request-submission} - -After a replica (i.e., a node belonging to an IC subnet) receives a call in an HTTP request to `/api/v2/canister//call` or `/api/v4/canister//call` -and if the replica accepts the call and subsequently the IC subnet (as a whole) receives the call, then the call gets added to the IC state as `Received`. - -This can only happen if the target canister is not frozen and - -- the target canister is not empty, the target canister is running, and ingress message inspection succeeds for calls to a regular canister; - -- the management canister method can be called via ingress messages and the caller is a controller of the target canister for calls to the management canister - (or the call targets the [IC Provisional API](#ic-provisional-api) on a development instance). - -Moreover, the signature must be valid and created with a correct key. Due to this check, the envelope is discarded after this point. - -Finally, the system time (of the replica receiving the HTTP request) must not have exceeded the `ingress_expiry` field of the HTTP request containing the call. - -Submitted request to `/api//canister//call` - -```html - -E : Envelope - -``` - -where `` is `v2` or `v4`. - -Conditions - -```html - -E.content.canister_id ∈ verify_envelope(E, E.content.sender, S.system_time) -if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: - if not (E.content.sender_info = null): - verify_signature E.sender_pubkey E.content.sender_info.sig ("\x0Eic-sender-info" · E.content.sender_info.info) - E.content.sender_info.signer = Signing_canister_id -else: - E.content.sender_info = null -if E.content.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): - if E.content.sender_info = null: - Caller_info_data = "" - Caller_info_signer = "" - else: - Caller_info_data = E.content.sender_info.info - Caller_info_signer = Signing_canister_id -else: - Caller_info_data = "" - Caller_info_signer = "" -|E.content.nonce| <= 32 -E.content ∉ dom(S.requests) -S.system_time <= E.content.ingress_expiry -is_effective_canister_id(E.content, ECID) -liquid_balance(S, E.content.canister_id) ≥ 0 -( E.content.canister_id = ic_principal - E.content.arg = candid({canister_id = CanisterId, …}) - E.content.sender ∈ S.controllers[CanisterId] - E.content.method_name ∈ - { "install_code", "install_chunked_code", "update_settings", - "upload_chunk", "stored_chunks", "clear_chunk_store", - "take_canister_snapshot", "load_canister_snapshot", "list_canister_snapshots", "delete_canister_snapshot", - "read_canister_snapshot_metadata", "read_canister_snapshot_data", "upload_canister_snapshot_metadata", "upload_canister_snapshot_data", - "provisional_top_up_canister" } -) ∨ ( - E.content.canister_id = ic_principal - E.content.arg = candid({canister_id = CanisterId, …}) - E.content.sender ∈ S.controllers[CanisterId] ∪ S.subnet_admins[S.canister_subnet[CanisterId]] - E.content.method_name ∈ - { "start_canister", "stop_canister", "uninstall_code", "delete_canister", "canister_status" } -) ∨ ( - E.content.canister_id = ic_principal - E.content.sender ∈ S.subnet_admins[S.canister_subnet[ECID]] - E.content.method_name ∈ - { "create_canister" } -) ∨ ( - E.content.canister_id = ic_principal - E.content.method_name ∈ - { "provisional_create_canister_with_cycles" } -) ∨ ( - E.content.canister_id ≠ ic_principal - S.canisters[E.content.canister_id] ≠ EmptyCanister - S.canister_status[E.content.canister_id] = Running - Env = { - time = S.time[E.content.canister_id]; - controllers = S.controllers[E.content.canister_id]; - global_timer = S.global_timer[E.content.canister_id]; - balance = S.balances[E.content.canister_id]; - reserved_balance = S.reserved_balances[E.content.canister_id]; - reserved_balance_limit = S.reserved_balance_limits[E.content.canister_id]; - compute_allocation = S.compute_allocation[E.content.canister_id]; - 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_snapshots = memory_usage_snapshots(S.snapshots[E.content.canister_id]); - freezing_threshold = S.freezing_threshold[E.content.canister_id]; - subnet_id = S.canister_subnet[E.content.canister_id].subnet_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]; - } - 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, Caller_info_data, Caller_info_signer, Env) = Return {status = Accept;} -) - -``` - -State after - -```html - -S with - requests[E.content] = (Received, ECID) - -``` - -:::note - -This is not instantaneous (the IC takes some time to agree it accepts the request) nor guaranteed (a node could just drop the request, or maybe it did not pass validation). But once the request has entered the IC state like this, it will be acted upon. - -::: - -#### Request rejection - -The IC may reject a received message for internal reasons (high load, low resources) or expiry. The precise conditions are not specified here, but the reject code must indicate this to be a system error. - -Conditions - -```html - -S.requests[R] = (Received, ECID) -Code = SYS_FATAL or Code = SYS_TRANSIENT - -``` - -State after - -```html - -S with - requests[R] = (Rejected (Code, Msg), ECID) - -``` - -#### Initiating canister calls - -A first step in processing a canister update call is to create a `CallMessage` in the message queue. - -The `request` field of the `FromUser` origin establishes the connection to the API message. One could use the corresponding `hash_of_map` for this purpose, but this formulation is more abstract. - -The IC does not make any guarantees about the order of incoming messages. - -Conditions - -```html - -S.requests[R] = (Received, ECID) -S.system_time <= R.ingress_expiry -C = S.canisters[R.canister_id] - -if R.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): - if R.sender_info = null: - Caller_info_data = "" - Caller_info_signer = "" - else: - Caller_info_data = R.sender_info.info - Caller_info_signer = Signing_canister_id -else: - Caller_info_data = "" - Caller_info_signer = "" - -``` - -State after - -```html - -S with - requests[R] = (Processing, ECID) - messages = - CallMessage { - origin = FromUser { request = R }; - caller = R.sender; - caller_info_data = Caller_info_data; - caller_info_signer = Caller_info_signer; - callee = R.canister_id; - method_name = R.method_name; - arg = R.arg; - transferred_cycles = 0; - queue = Unordered; - } · S.messages - -``` - -#### Calls to stopped/stopping canisters are rejected - -A call to a canister which is stopping, or stopped is automatically rejected. - -Conditions - -```html - -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] = Stopped or S.canister_status[CM.callee] = Stopping -``` - -State after: - -```html - -messages = Older_messages · Younger_messages · - ResponseMessage { - origin = CM.origin; - response = Reject (CANISTER_ERROR, ); - refunded_cycles = CM.transferred_cycles; - } - -``` - -#### Calls to frozen canisters are rejected - -A call to a canister which is frozen is automatically rejected. - -Conditions - -```html - -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 -liquid_balance(S, CM.callee) < 0 -``` - -State after: - -```html - -messages = Older_messages · Younger_messages · - ResponseMessage { - origin = CM.origin; - response = Reject (SYS_TRANSIENT, ); - refunded_cycles = CM.transferred_cycles; - } - -``` - -#### Call context creation {#call-context-creation} - -Before invoking a heartbeat, a global timer, or a message to a public entry point, a call context is created for bookkeeping purposes. For these invocations the canister must be running (so not stopped or stopping). Additionally, these invocations only happen for "real" canisters, not the IC management canister. - -This "bookkeeping transition" must be immediately followed by the corresponding ["Message execution" transition](#rule-message-execution). - -*Call context creation: Public entry points* - -For a message to a public entry point, the method is looked up in the list of exports. This happens for both ingress and inter-canister messages. - -The position of the message in the queue is unchanged. - -Conditions - -```html - -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, CM.callee) ≥ MAX_CYCLES_PER_MESSAGE -Ctxt_id ∉ dom(S.call_contexts) - -``` - -State after - -```html - -S with - messages = - Older_messages · - FuncMessage { - call_context = Ctxt_id; - caller = CM.caller; - caller_info_data = CM.caller_info_data; - caller_info_signer = CM.caller_info_signer; - receiver = CM.callee; - entry_point = PublicMethod CM.method_name CM.caller CM.arg; - queue = CM.queue; - } · - Younger_messages - call_contexts[Ctxt_id] = { - canister = CM.callee; - origin = CM.origin; - needs_to_respond = true; - deleted = false; - available_cycles = CM.transferred_cycles; - } - balances[CM.callee] = S.balances[CM.callee] - MAX_CYCLES_PER_MESSAGE - -``` - -*Call context creation: Heartbeat* - -If canister `C` exports a method with name `canister_heartbeat`, the IC will create the corresponding call context. - -Conditions - -```html - -S.canisters[C] ≠ EmptyCanister -S.canister_status[C] = Running -liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE -Ctxt_id ∉ dom(S.call_contexts) - -``` - -State after - -```html - -S with - messages = - FuncMessage { - call_context = Ctxt_id; - caller = ic_principal; - caller_info_data = ""; - caller_info_signer = ""; - receiver = C; - entry_point = Heartbeat; - queue = Queue { from = System; to = C }; - } - · S.messages - call_contexts[Ctxt_id] = { - canister = C; - origin = FromSystemTask; - needs_to_respond = false; - deleted = false; - available_cycles = 0; - } - balances[C] = S.balances[C] - MAX_CYCLES_PER_MESSAGE - -``` - -*Call context creation: Global timer* - -If canister `C` exports a method with name `canister_global_timer`, the global timer of canister `C` is set, and the current time for canister `C` has passed the value of the global timer, the IC will create the corresponding call context and deactivate the global timer. - -Conditions - -```html - -S.canisters[C] ≠ EmptyCanister -S.canister_status[C] = Running -S.global_timer[C] ≠ 0 -S.time[C] ≥ S.global_timer[C] -liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE -Ctxt_id ∉ dom(S.call_contexts) - -``` - -State after - -```html - -S with - messages = - FuncMessage { - call_context = Ctxt_id; - caller = ic_principal; - caller_info_data = ""; - caller_info_signer = ""; - receiver = C; - entry_point = GlobalTimer; - queue = Queue { from = System; to = C }; - } - · S.messages - call_contexts[Ctxt_id] = { - canister = C; - origin = FromSystemTask; - needs_to_respond = false; - deleted = false; - available_cycles = 0; - } - global_timer[C] = 0 - balances[C] = S.balances[C] - MAX_CYCLES_PER_MESSAGE - -``` - -*Call context creation: On low wasm memory* - -If `S.on_low_wasm_memory_hook_status[C]` is `Ready` for a canister `C`, the IC will create the corresponding call context and set `S.on_low_wasm_memory_hook_status[C]` to `Executed`. - -Conditions - -```html - -S.canisters[C] ≠ EmptyCanister -S.canister_status[C] = Running -S.on_low_wasm_memory_hook_status[C] = Ready -liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE -Ctxt_id ∉ dom(S.call_contexts) - -``` - -State after - -```html - -S with - messages = - FuncMessage { - call_context = Ctxt_id; - caller = ic_principal; - caller_info_data = ""; - caller_info_signer = ""; - receiver = C; - entry_point = OnLowWasmMemory; - queue = Queue { from = System; to = C }; - } - · S.messages - call_contexts[Ctxt_id] = { - canister = C; - origin = FromSystemTask; - needs_to_respond = false; - deleted = false; - available_cycles = 0; - } - on_low_wasm_memory_hook_status[C] = Executed - balances[C] = S.balances[C] - MAX_CYCLES_PER_MESSAGE - -``` - -The IC can execute any message that is at the head of its queue, i.e. there is no older message with the same abstract `queue` field. The actual message execution, if successful, may enqueue further messages and --- if the function returns a response --- record this response. The new call and response messages are enqueued at the end. - -Note that new messages are executed only if the canister is Running and is not frozen. - -#### Scheduling on low wasm memory hook {#rule-on-low-wasm-memory} - -This transition is executed immediately after [Message execution](#rule-message-execution) and IC Management Canister execution (update call). - -Conditions - -```html -if S.wasm_memory_limit[C] < |S.canisters[C].wasm_state.wasm_memory| + S.wasm_memory_threshold[C]: - if S.on_low_wasm_memory_hook_status[C] = ConditionNotSatisfied: - On_low_wasm_memory_hook_status = Ready - else: - On_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[C] -else: - On_low_wasm_memory_hook_status = ConditionNotSatisfied -``` - -State after - -```html -S with - on_low_wasm_memory_hook_status[C] = On_low_wasm_memory_hook_status -``` - -#### Message execution {#rule-message-execution} - -The transition models the actual execution of a message, whether it is an initial call to a public method or a response. In either case, a call context already exists (see transition "Call context creation"). - -For convenience, we first define a function that extracts the deadline from a call context. Note that user and system messages have no deadline. - -```html - -deadline_of_context(ctxt) = match ctxt.origin with - FromCanister O if O.deadline ≠ Expired _ → O.deadline - FromCanister O if O.deadline = Expired ts → ts - otherwise → NoDeadline - -``` - -Conditions - -```html - -S.messages = Older_messages · FuncMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -(∀ FuncMessage M' ∈ Older_messages · Younger_messages. M'.receiver ≠ M.receiver or M.entry_point ≠ OnLowWasmMemory) -S.on_low_wasm_memory_hook_status[M.receiver] ≠ Ready -S.canisters[M.receiver] ≠ EmptyCanister -Mod = S.canisters[M.receiver].module -Ctxt = S.call_contexts[M.call_context] -Deadline = deadline_of_context(Ctxt) - -Is_response = M.entry_point == Callback _ _ _ - -Env = { - time = S.time[M.receiver]; - controllers = S.controllers[M.receiver]; - global_timer = S.global_timer[M.receiver]; - balance = S.balances[M.receiver] - reserved_balance = S.reserved_balances[M.receiver]; - reserved_balance_limit = S.reserved_balance_limits[M.receiver]; - compute_allocation = S.compute_allocation[M.receiver]; - 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_snapshots = memory_usage_snapshots(S.snapshots[M.receiver]); - freezing_threshold = S.freezing_threshold[M.receiver]; - subnet_id = S.canister_subnet[M.receiver].subnet_id; - subnet_size = S.canister_subnet[M.receiver].subnet_size; - certificate = NoCertificate; - status = simple_status(S.canister_status[M.receiver]); - canister_version = S.canister_version[M.receiver]; -} - -Available = Ctxt.available_cycles -( M.entry_point = PublicMethod Name Caller Arg - F = Mod.update_methods[Name](Arg, M.caller, M.caller_info_data, M.caller_info_signer, Deadline, Env, Available) - New_canister_version = S.canister_version[M.receiver] + 1 - Wasm_memory_limit = S.wasm_memory_limit[M.receiver] -) -or -( M.entry_point = PublicMethod Name Caller Arg - F = query_as_update(Mod.query_methods[Name], Arg, M.caller, M.caller_info_data, M.caller_info_signer, Env, Available) - New_canister_version = S.canister_version[M.receiver] - Wasm_memory_limit = 0 -) -or -( M.entry_point = Callback Callback Response RefundedCycles - F = Mod.callbacks(Callback, M.caller, M.caller_info_data, M.caller_info_signer, Response, Deadline, RefundedCycles, Env, Available) - New_canister_version = S.canister_version[M.receiver] + 1 - Wasm_memory_limit = 0 -) -or -( M.entry_point = Heartbeat - F = system_task_as_update(Mod.heartbeat, Env) - New_canister_version = S.canister_version[M.receiver] + 1 - Wasm_memory_limit = 0 -) -or -( M.entry_point = GlobalTimer - F = system_task_as_update(Mod.global_timer, Env) - New_canister_version = S.canister_version[M.receiver] + 1 - Wasm_memory_limit = 0 -) -or -( M.entry_point = OnLowWasmMemory - F = system_task_as_update(Mod.on_low_wasm_memory, Env) - New_canister_version = S.canister_version[M.receiver] + 1 - Wasm_memory_limit = 0 -) - -R = F(S.canisters[M.receiver].wasm_state) - -``` - -State after - -```html - -if - R = Return res - validate_sender_canister_version(res.new_calls, S.canister_version[M.receiver]) - res.cycles_used ≤ (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE) - 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], 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 ]) - - Cycles_reserved - New_reserved_balance = S.reserved_balances[M.receiver] + Cycles_reserved - Min_balance = if Is_response then 0 else freezing_limit( - S.compute_allocation[M.receiver], - S.memory_allocation[M.receiver], - 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_chunk_store(S.chunk_store[M.receiver]) + - memory_usage_snapshots(S.snapshots[M.receiver]), - S.canister_subnet[M.receiver].subnet_size, - ) - New_reserved_balance ≤ S.reserved_balance_limits[M.receiver] - liquid_balance( - New_balance, - New_reserved_balance, - Min_balance - ) ≥ 0 - (Wasm_memory_limit = 0) or |res.new_state.wasm_memory| <= Wasm_memory_limit - (res.response = NoResponse) or Ctxt.needs_to_respond -then - S with - canisters[M.receiver].wasm_state = res.new_state; - canister_version[M.receiver] = New_canister_version; - messages = - Older_messages · - Younger_messages · - [ CallMessage { - origin = FromCanister { - call_context = M.call_context; - callback = call.callback; - deadline = if call.timeout_seconds ≠ NoTimeout - then S.time[M.receiver] + call.timeout_seconds * 10^9 - else NoDeadline - - }; - caller = M.receiver; - caller_info_data = ""; - caller_info_signer = ""; - callee = call.callee; - method_name = call.method_name; - arg = call.arg; - transferred_cycles = call.transferred_cycles - queue = Queue { from = M.receiver; to = call.callee }; - } - | call ∈ res.new_calls ] · - [ ResponseMessage { - origin = Ctxt.origin; - response = res.response; - refunded_cycles = Available - res.cycles_accepted; - } - | res.response ≠ NoResponse ] - - if res.response = NoResponse: - call_contexts[M.call_context].available_cycles = Available - res.cycles_accepted - else - call_contexts[M.call_context].needs_to_respond = false - call_contexts[M.call_context].available_cycles = 0 - - if res.new_certified_data ≠ NoCertifiedData: - certified_data[M.receiver] = res.new_certified_data - - if res.new_global_timer ≠ NoGlobalTimer: - global_timer[M.receiver] = res.new_global_timer - - balances[M.receiver] = New_balance - reserved_balances[M.receiver] = New_reserved_balance - - canister_logs[M.receiver] = S.canister_logs[M.receiver] · canister_logs -else - S with - messages = Older_messages · Younger_messages - balances[M.receiver] = - (S.balances[M.receiver] + (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE)) - - min (R.cycles_used, (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE)) - -``` - -Depending on whether this is a call message and a response messages, we have either set aside `MAX_CYCLES_PER_MESSAGE` or `MAX_CYCLES_PER_RESPONSE`, either in the call context creation rule or the Callback invocation rule. - -The cycle consumption of executing this message is modeled via the unspecified `cycles_used` variable; the variable takes some value between 0 and `MAX_CYCLES_PER_MESSAGE`/`MAX_CYCLES_PER_RESPONSE` (for call execution and response execution, respectively). - -The logs produced by the canister during message execution are modeled via the unspecified `canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[M.receiver]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps. - -This transition detects certain behavior that will appear as a trap (and which an implementation may implement by trapping directly in a system call): - -- Responding if the present call context does not need to be responded to - -- Accepting more cycles than are available on the call context - -- Sending out more cycles than available to the canister - -- Consuming more cycles than allowed (and reserved) - -If message execution [*traps* (in the sense of a Wasm function)](#system-api-module), the message gets dropped. No response is generated (as some other message may still fulfill this calling context). Any state mutation is discarded. If the message was a call, the associated cycles are held by its associated call context and will be refunded to the caller, see [Call context starvation](#rule-starvation). - -If message execution [*returns* (in the sense of a Wasm function)](#system-api-module), the state is updated and possible outbound calls and responses are enqueued. - -Note that returning does *not* imply that the call associated with this message now *succeeds* in the sense defined in [section responding](#responding); that would require a (unique) call to `ic0.reply`. Note also that the state changes are persisted even when the IC is set to synthesize a [CANISTER\_ERROR](#reject-codes) reject immediately afterward (which happens when this returns without calling `ic0.reply` or `ic0.reject`, the corresponding call has not been responded to and there are no outstanding callbacks, see [Call context starvation](#rule-starvation)). - -The function `validate_sender_canister_version` checks that `sender_canister_version` matches the actual canister version of the sender in all calls to the methods of the management canister that take `sender_canister_version`: -``` -validate_sender_canister_version(new_calls, canister_version_from_system) = - ∀ call ∈ new_calls. (call.callee = ic_principal and (call.method = 'create_canister' or call.method = 'update_settings' or call.method = 'install_code' or call.method = `install_chunked_code` or call.method = 'uninstall_code' or call.method = 'provisional_create_canister_with_cycles') and call.arg = candid(A) and A.sender_canister_version = n) => n = canister_version_from_system -``` - -The functions `query_as_update` and `system_task_as_update` turns a query function (note that composite query methods cannot be called when executing a message during this transition) resp the heartbeat or global timer into an update function; this is merely a notational trick to simplify the rule: -``` -query_as_update(f, arg, caller, caller_info_data, caller_info_signer, env, available) = λ wasm_state → - match f(arg, caller, caller_info_data, caller_info_signer, env, available)(wasm_state) with - Trap trap → Trap trap - Return res → Return { - new_state = wasm_state; - new_calls = []; - new_certified_data = NoCertifiedData; - new_global_timer = NoGlobalTimer; - response = res.response; - cycles_accepted = res.cycles_accepted; - cycles_used = res.cycles_used; - } - -system_task_as_update(f, env) = λ wasm_state → - match f(env)(wasm_state) with - Trap trap → Trap trap - Return res → Return { - new_state = res.new_state; - new_calls = res.new_calls; - new_certified_data = res.new_certified_data; - new_global_timer = res.new_global_timer; - response = NoResponse; - cycles_accepted = 0; - cycles_used = res.cycles_used; - } -``` - -Note that by construction, a query function will either trap or return with a response; it will never send calls, and it will never change the state of the canister. - -#### Spontaneous request rejection {#request-rejection} - -The system can reject a request at any point in time, e.g., because it is overloaded. - -Condition: -```html -S.messages = Older_messages · CallMessage CM · Younger_messages -``` - -State after, with `reject_code` being an arbitrary reject code: -```html -S.messages = - Older_messages - · Younger_messages - · ResponseMessage { - origin = CM.origin; - response = Reject (reject_code, ); - refunded_cycles = CM.transferred_cycles; - } -``` - -#### Call expiry {#call-expiry} - -These transitions expire bounded-wait calls. The transition can be taken before the specified call deadline (e.g., due to high system load), and we thus ignore the caller time in these transitions. We define two variants of the transition, one that expires messages, and one that expires calls that are in progress (i.e., have open downstream call contexts). - -The first transition defines the expiry of messages. - -Condition: - -```html -S.messages = Older_messages · M · Younger_messages -M = CallMessage _ ∨ M = ResponseMessage _ -M.origin = FromCanister O -O.deadline = timestamp -``` - -State after: - -```html -S.messages = Older_messages · (M with origin = FromCanister O with deadline = Expired timestamp) · Younger_messages · - ResponseMessage { - origin = FromCanister O with deadline = NoDeadline; - response = Reject (SYS_UNKNOWN, ); - refunded_cycles = 0; - } -``` - -The next two transitions define the expiry of calls that are being processed by the callee. The first transition deals with regular calls. - -Condition: - -```html -ctxt_id ∈ S.call_contexts -S.call_contexts[ctxt_id].origin = FromCanister O -S.call_contexts[ctxt_id].needs_to_respond = true -O.deadline = timestamp -``` - -State after: - -```html -S.call_contexts[ctxt_id].origin = FromCanister O with deadline = Expired timestamp -S.messages = S.messages · ResponseMessage { - origin = FromCanister O with deadline = NoDeadline; - response = Reject (SYS_UNKNOWN, ); - refunded_cycles = 0; -} -``` - -The second transition deals with the special case of a call that's trying to stop the `target_canister` - -Condition: - -```html -S.canister_status[target_canister] = Stopping (prefix · (FromCanister O, stop_ts) · suffix) -O.deadline = timestamp -``` - -State after: - -```html -S.canister_status[target_canister] = - Stopping (prefix · (FromCanister O with deadline = Expired timestamp, stop_ts) · suffix) -S.messages = S.messages · ResponseMessage { - origin = FromCanister O with deadline = NoDeadline; - response = Reject (SYS_UNKNOWN, ); - refunded_cycles = 0; -} -``` - -#### Call context starvation {#rule-starvation} - -If the call context needs to respond (in particular, if the call context is not for a system task) and there is no call, downstream call context, or response that references a call context, then a reject is synthesized. The error message below is *not* indicative. In particular, if the IC has an idea about *why* this starved, it can put that in there (e.g. the initial message handler trapped with an out-of-memory access). - -Conditions - -```html - -S.call_contexts[Ctxt_id].needs_to_respond = true -∀ CallMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ -∀ ResponseMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ -∀ (_ ↦ {needs_to_respond = true, origin = FromCanister O, …}) ∈ S.call_contexts: O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ -∀ (_ ↦ Stopping Origins) ∈ S.canister_status: ∀(FromCanister O, _) ∈ Origins. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ - -``` - -State after - -```html - -S with - call_contexts[Ctxt_id].needs_to_respond = false - call_contexts[Ctxt_id].available_cycles = 0 - messages = - S.messages · - ResponseMessage { - origin = S.call_contexts[Ctxt_id].origin; - response = Reject (CANISTER_ERROR, ); - refunded_cycles = S.call_contexts[Ctxt_id].available_cycles; - } - -``` - -#### Call context removal {#call-context-removal} - -If there is no call, downstream call context, or response that references a call context, and the call context does not need to respond (because it has already responded or its origin is a system task that does not await a response), then the call context can be removed. - -Conditions - -```html - -S.call_contexts[Ctxt_id].needs_to_respond = false -∀ CallMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ -∀ ResponseMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ -∀ (_ ↦ {needs_to_respond = true, origin = FromCanister O, …}) ∈ S.call_contexts: O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ -∀ (_ ↦ Stopping Origins) ∈ S.canister_status: ∀(FromCanister O, _) ∈ Origins. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ - -``` - -State after - -```html - -S with - call_contexts[Ctxt_id] = (deleted) - -``` - -#### IC Management Canister: Canister creation - -The IC chooses an appropriate canister id (referred to as `CanisterId`) and subnet id (referred to as `SubnetId`, `SubnetId ∈ Subnets`, where `Subnets` is the under-specified set of subnet ids on the IC) and instantiates a new (empty) canister identified by `CanisterId` on the subnet identified by `SubnetId` with subnet size denoted by `SubnetSize`. The *controllers* are set such that the sender of this request is the only controller, unless the `settings` say otherwise. All cycles on this call are now the canister's initial cycles. - -This is also when the System Time of the new canister starts ticking. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'create_canister' -M.arg = candid(A) -is_system_assigned Canister_id -Canister_id ∉ dom(S.canisters) -SubnetId ∈ Subnets -(A.settings.environment_variables = null or - (|A.settings.environment_variables| ≤ MAX_ENV_VAR_COUNT and - ∀(name, value) ∈ A.settings.environment_variables: - |name| ≤ MAX_ENV_VAR_NAME_LENGTH and - |value| ≤ MAX_ENV_VAR_VALUE_LENGTH and - is_valid_utf8(name) and - is_valid_utf8(value))) - -if A.settings.controllers is not null: - New_controllers = A.settings.controllers -else: - New_controllers = [M.caller] - -if A.settings.compute_allocation is not null: - New_compute_allocation = A.settings.compute_allocation -else: - New_compute_allocation = 0 -if A.settings.memory_allocation is not null: - New_memory_allocation = A.settings.memory_allocation -else: - New_memory_allocation = 0 -if A.settings.freezing_threshold is not null: - New_freezing_threshold = A.settings.freezing_threshold -else: - New_freezing_threshold = 2592000 -if A.settings.reserved_cycles_limit is not null: - New_reserved_balance_limit = A.settings.reserved_cycles_limit -else: - New_reserved_balance_limit = 5_000_000_000_000 -if A.settings.wasm_memory_limit is not null: - New_wasm_memory_limit = A.settings.wasm_memory_limit -else: - New_wasm_memory_limit = 0 -if A.settings.wasm_memory_threshold is not null: - New_wasm_memory_threshold = A.settings.wasm_memory_threshold -else: - New_wasm_memory_threshold = 0 -if A.settings.environment_variables is not null: - New_environment_variables = A.settings.environment_variables -else: - New_environment_variables = [] - -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(S', Canister_id) ≥ 0 - -New_canister_history = { - total_num_changes = 1 - recent_changes = { - timestamp_nanos = CurrentTime - canister_version = 0 - origin = change_origin(M.caller, A.sender_canister_version, M.origin) - details = Creation { - controllers = New_controllers - environment_variables_hash = if A.settings.environment_variables is not null then - opt hash_of_map(A.settings.environment_variables) - else - null - } - } -} - -if A.settings.log_visibility is not null: - New_canister_log_visibility = A.settings.log_visibility -else: - New_canister_log_visibility = Controllers - -if A.settings.snapshot_visibility is not null: - New_canister_snapshot_visibility = A.settings.snapshot_visibility -else: - New_canister_snapshot_visibility = Controllers -``` - -State after - -```html - -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 - chunk_store[Canister_id] = () - compute_allocation[Canister_id] = New_compute_allocation - memory_allocation[Canister_id] = New_memory_allocation - freezing_threshold[Canister_id] = New_freezing_threshold - balances[Canister_id] = New_balance - reserved_balances[Canister_id] = New_reserved_balance - reserved_balance_limits[Canister_id] = New_reserved_balance_limit - wasm_memory_limit[Canister_id] = New_wasm_memory_limit - wasm_memory_threshold[Canister_id] = New_wasm_memory_threshold - environment_variables[Canister_id] = New_environment_variables - on_low_wasm_memory_hook_status[Canister_id] = ConditionNotSatisfied - certified_data[Canister_id] = "" - query_stats[Canister_id] = [] - canister_history[Canister_id] = New_canister_history - canister_log_visibility[Canister_id] = New_canister_log_visibility - canister_snapshot_visibility[Canister_id] = New_canister_snapshot_visibility - canister_logs[Canister_id] = [] - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid({canister_id = Canister_id})) - refunded_cycles = 0 - } - canister_status[Canister_id] = Running - canister_version[Canister_id] = 0 - canister_subnet[Canister_id] = Subnet { - subnet_id : SubnetId - subnet_size : SubnetSize - } - -``` - -This uses the predicate -``` -is_system_assigned : Principal -> Bool -``` -which characterizes all system-assigned ids. - -To avoid clashes with potential user ids or is derived from users or canisters, we require (somewhat handwavy) that - -- `is_system_assigned (mk_self_authenticating_id pk) = false` for possible public keys `pk` and - -- `is_system_assigned (mk_derived_id p dn) = false` for any `p` that could be a user id or canister id. - -- `is_system_assigned p = false` for `|p| > 29`. - -- `is_system_assigned ic_principal = false`. - -#### IC Management Canister: Changing settings - -Only the controllers of the given canister can update the canister settings. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'update_settings' -M.arg = candid(A) -M.caller ∈ S.controllers[A.canister_id] -(A.settings.environment_variables = null or - (|A.settings.environment_variables| ≤ MAX_ENV_VAR_COUNT and - ∀(name, value) ∈ A.settings.environment_variables: - |name| ≤ MAX_ENV_VAR_NAME_LENGTH and - |value| ≤ MAX_ENV_VAR_VALUE_LENGTH and - is_valid_utf8(name) and - is_valid_utf8(value))) - -if New_wasm_memory_limit > 0: - |S.canisters[A.canister_id].wasm_state.wasm_memory| ≤ New_wasm_memory_limit - -if A.settings.compute_allocation is not null: - New_compute_allocation = A.settings.compute_allocation -else: - New_compute_allocation = S.compute_allocation[A.canister_id] -if A.settings.memory_allocation is not null: - New_memory_allocation = A.settings.memory_allocation -else: - New_memory_allocation = S.memory_allocation[A.canister_id] -if A.settings.freezing_threshold is not null: - New_freezing_threshold = A.settings.freezing_threshold -else: - New_freezing_threshold = S.freezing_threshold[A.canister_id] -if A.settings.reserved_cycles_limit is not null: - New_reserved_balance_limit = A.settings.reserved_cycles_limit -else: - New_reserved_balance_limit = S.reserved_balance_limits[A.canister_id] -if A.settings.wasm_memory_limit is not null: - New_wasm_memory_limit = A.settings.wasm_memory_limit -else: - New_wasm_memory_limit = S.wasm_memory_limit[A.canister_id] -if A.settings.wasm_memory_threshold is not null: - New_wasm_memory_threshold = A.settings.wasm_memory_threshold -else: - New_wasm_memory_threshold = S.wasm_memory_threshold[A.canister_id] -if A.settings.environment_variables is not null: - New_environment_variables = A.settings.environment_variables -else: - New_environment_variables = S.environment_variables[A.canister_id] - -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(S', A.canister_id) ≥ 0 - -S.canister_history[A.canister_id] = { - total_num_changes = N; - recent_changes = H; -} - -if A.settings.controllers is not null: - 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 = ControllersChange { - controllers = A.settings.controllers; - }; - }; - } -else: - New_canister_history = S.canister_history[A.canister_id] - -``` - -State after - -```html - -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 - compute_allocation[A.canister_id] = New_compute_allocation - memory_allocation[A.canister_id] = New_memory_allocation - freezing_threshold[A.canister_id] = New_freezing_threshold - balances[A.canister_id] = New_balance - reserved_balances[A.canister_id] = New_reserved_balance - reserved_balance_limits[A.canister_id] = New_reserved_balance_limit - wasm_memory_limit[A.canister_id] = New_wasm_memory_limit - wasm_memory_threshold[A.canister_id] = New_wasm_memory_threshold - environment_variables[A.canister_id] = New_environment_variables - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - if A.settings.log_visibility is not null: - canister_log_visibility[A.canister_id] = A.settings.log_visibility - if A.settings.snapshot_visibility is not null: - canister_snapshot_visibility[A.canister_id] = A.settings.snapshot_visibility - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid()) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Canister status - -The controllers of a canister can obtain detailed information about the canister. - -Given a state `S` and `Canister_id`, we define - -```html - -canister_status(S, Canister_id) = - { - status = simple_status(S.canister_status[Canister_id]); - settings = { - controllers = S.controllers[Canister_id]; - compute_allocation = S.compute_allocation[Canister_id]; - memory_allocation = S.memory_allocation[Canister_id]; - freezing_threshold = S.freezing_threshold[Canister_id]; - reserved_cycles_limit = S.reserved_balance_limit[Canister_id]; - wasm_memory_limit = S.wasm_memory_limit[Canister_id]; - wasm_memory_threshold = S.wasm_memory_threshold[Canister_id]; - environment_variables = S.environment_variables[Canister_id]; - } - module_hash = - if S.canisters[Canister_id] = EmptyCanister - then null - else opt (SHA-256(S.canisters[Canister_id].raw_module)); - memory_size = Memory_usage; - memory_metrics = Memory_metrics; - cycles = S.balances[Canister_id]; - reserved_cycles = S.reserved_balances[Canister_id] - idle_cycles_burned_per_day = idle_cycles_burned_rate( - S.compute_allocation[Canister_id], - S.memory_allocation[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]) + - memory_usage_chunk_store(S.chunk_store[Canister_id]) + - memory_usage_snapshots(S.snapshots[Canister_id]), - S.freezing_threshold[Canister_id], - S.canister_subnet[Canister_id].subnet_size, - ); - query_stats = noise(SUM {{num_calls_total: 1, - num_instructions_total: single_query_stats.num_instructions, - request_payload_bytes_total: single_query_stats.request_payload_bytes, - response_payload_bytes_total: single_query_stats.response_payload_bytes} | - single_query_stats <- S.query_stats[Canister_id]; - single_query_stats.timestamp <= S.time[Canister_id] - T}) - } - -``` - -where -- `Memory_usage` is the (in this specification underspecified) total canister memory usage in bytes; -- `Memory_metrics` are the (in this specification underspecified) detailed metrics on the memory consumption of the canister (see [Memory Metrics](#ic-canister_status-memory_metrics) for more details); -- `T` is an (in this specification underspecified) time delay of query statistics and `noise` is an (in this specification underspecified) probabilistic function -modelling information loss due to aggregating query statistics in a distributed system. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'canister_status' -M.arg = candid(A) -M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = candid(canister_status(S, A.canister_id)) - refunded_cycles = M.transferred_cycles - } - -``` - -The IC method `canister_status` can also be invoked via management canister query calls. -They are calls to `/api/v3/canister//query` -with CBOR content `Q` such that `Q.canister_id = ic_principal`. - -Submitted request to `/api/v3/canister//query` - -```html - -E : Envelope - -``` - -Conditions - -```html - -E.content = CanisterQuery Q -Q.canister_id = ic_principal -Q.method_name = 'canister_status' -|Q.nonce| <= 32 -is_effective_canister_id(E.content, ECID) -S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id -Q.arg = candid(A) -A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) -if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: - if not (Q.sender_info = null): - verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) - Q.sender_info.signer = Signing_canister_id -else: - Q.sender_info = null -Q.sender ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -Query response `R`: - -```html - -{status: "replied"; reply: {arg: candid(canister_status(S, A.canister_id))}, signatures: Sigs} - -``` - -where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: - -```html - -verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" - -``` - -#### IC Management Canister: Canister information - -Every canister can retrieve the canister history, current module hash, and current controllers of every other canister (including itself). - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'canister_info' -M.arg = candid(A) -if A.num_requested_changes = null then From = |S.canister_history[A.canister_id].recent_changes| -else From = max(0, |S.canister_history[A.canister_id].recent_changes| - A.num_requested_changes) -End = |S.canister_history[A.canister_id].recent_changes| - 1 - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = candid({ - total_num_changes = S.canister_history[A.canister_id].total_num_changes; - recent_changes = S.canister_history[A.canister_id].recent_changes[From..End]; - module_hash = - if S.canisters[A.canister_id] = EmptyCanister - then null - else opt (SHA-256(S.canisters[A.canister_id].raw_module)); - controllers = S.controllers[A.canister_id]; - }) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Canister metadata - -Every canister can retrieve public metadata of every other canister (including itself) -and private metadata of canisters controlled by the caller. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'canister_metadata' -M.arg = candid(A) -A.name ∈ dom(S.canisters[A.canister_id].public_custom_sections) ∨ (A.name ∈ dom(S.canisters[A.canister_id].private_custom_sections) ∧ M.caller ∈ S.controllers[A.canister_id]) -if A.name ∈ dom(S.canisters[A.canister_id].public_custom_sections): - Content = S.canisters[A.canister_id].public_custom_sections[A.name] -else: - Content = S.canisters[A.canister_id].private_custom_sections[A.name] - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = candid({ - value = Content; - }) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Upload Chunk - -A controller of a canister, or the canister itself can upload chunks to the chunk store of that canister. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'upload_chunk' -M.arg = candid(A) -|dom(S.chunk_store[A.canister_id]) ∪ {SHA-256(A.chunk)}| <= CHUNK_STORE_SIZE -M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} - - -``` - -State after - -```html - -S with - chunk_store[A.canister_id](SHA-256(A.chunk)) = A.chunk - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = candid({hash: hash}) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Clear chunk store - -The controller of a canister, or the canister itself can clear the chunk store of that canister. - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'clear_chunk_store' -M.arg = candid(A) -M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} -``` - -State after - -```html - -S with - chunk_store[A.canister_id] = () - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = candid() - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: List stored chunks - -The controller of a canister, or the canister itself can list the hashes of the chunks stored in the chunk store. - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'stored_chunks' -M.arg = candid(A) -M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = candid([{hash: hash} | hash <- dom(S.chunk_store[A.canister_id])]) - refunded_cycles = M.transferred_cycles - } - -``` - - - - -#### IC Management Canister: Code installation - -Only the controllers of the given canister can install code. This transition installs new code over a canister. This involves invoking the `canister_init` method (see [Canister initialization](#system-api-init)), which must succeed. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'install_code' -M.arg = candid(A) -Mod = parse_wasm_mod(A.wasm_module) -Public_custom_sections = parse_public_custom_sections(A.wasm_module); -Private_custom_sections = parse_private_custom_sections(A.wasm_module); -(A.mode = install and S.canisters[A.canister_id] = EmptyCanister) or A.mode = reinstall -M.caller ∈ S.controllers[A.canister_id] - -dom(Mod.update_methods) ∩ dom(Mod.query_methods) = ∅ -dom(Mod.update_methods) ∩ dom(Mod.composite_query_methods) = ∅ -dom(Mod.query_methods) ∩ dom(Mod.composite_query_methods) = ∅ - -Env = { - time = S.time[A.canister_id]; - controllers = S.controllers[A.canister_id]; - global_timer = 0; - balance = S.balances[A.canister_id]; - reserved_balance = S.reserved_balances[A.canister_id]; - reserved_balance_limit = S.reserved_balance_limits[A.canister_id]; - compute_allocation = S.compute_allocation[A.canister_id]; - 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_snapshots = memory_usage_snapshots(S.snapshots[A.canister_id]); - freezing_threshold = S.freezing_threshold[A.canister_id]; - subnet_id = S.canister_subnet[A.canister_id].subnet_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] + 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], 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, A.canister_id) ≥ MAX_CYCLES_PER_MESSAGE - -liquid_balance(S', A.canister_id) ≥ 0 - -(S.wasm_memory_limit[A.canister_id] = 0) or |New_state.wasm_memory| <= S.wasm_memory_limit[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 = CodeDeployment { - mode = A.mode; - module_hash = SHA-256(A.wasm_module); - }; - }; -} - -``` - -State after - -```html - -S' = S with - canisters[A.canister_id] = { - wasm_state = New_state; - module = Mod; - raw_module = A.wasm_module; - public_custom_sections = Public_custom_sections; - private_custom_sections = Private_custom_sections; - } - certified_data[A.canister_id] = New_certified_data - if New_global_timer ≠ NoGlobalTimer: - global_timer[A.canister_id] = New_global_timer - else: - global_timer[A.canister_id] = 0 - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - balances[A.canister_id] = New_balance - reserved_balances[A.canister_id] = New_reserved_balance - canister_history[A.canister_id] = New_canister_history - canister_logs[A.canister_id] = New_canister_logs - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin; - response = Reply (candid()); - refunded_cycles = M.transferred_cycles; - } - -``` - -The logs produced by the canister during the execution of the WebAssembly `start` and `canister_init` functions are modeled via the unspecified `New_canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[A.canister_id]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps. - -#### IC Management Canister: Code upgrade - -Only the controllers of the given canister can install new code. This changes the code of an *existing* canister, preserving the state in the stable memory. This involves invoking the `canister_pre_upgrade` method, if the `skip_pre_upgrade` flag is not set to `opt true`, on the old and `canister_post_upgrade` method on the new canister, which must succeed and must not invoke other methods. If the `wasm_memory_persistence` flag is set to `opt keep`, then the WebAssembly memory is preserved. - -If the old canister module exports a private custom section with the name "enhanced-orthogonal-persistence", then the `wasm_memory_persistence` option must be set to `opt keep` or `opt replace`, i.e., the option must not be `null`. - -If the `wasm_memory_persistence` option is set to `opt keep`, then the new canister module must export a private custom section with the name "enhanced-orthogonal-persistence". - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'install_code' -M.arg = candid(A) -Mod = parse_wasm_mod(A.wasm_module) -Public_custom_sections = parse_public_custom_sections(A.wasm_module) -Private_custom_sections = parse_private_custom_sections(A.wasm_module) -M.caller ∈ S.controllers[A.canister_id] -S.canisters[A.canister_id] = { wasm_state = Old_state; module = Old_module, …} - -dom(Mod.update_methods) ∩ dom(Mod.query_methods) = ∅ -dom(Mod.update_methods) ∩ dom(Mod.composite_query_methods) = ∅ -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]; - compute_allocation = S.compute_allocation[A.canister_id]; - 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_snapshots = memory_usage_snapshots(S.snapshots[A.canister_id]); - freezing_threshold = S.freezing_threshold[A.canister_id]; - subnet_id = S.canister_subnet[A.canister_id].subnet_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]; -} - -( - (A.mode = upgrade U and U.skip_pre_upgrade ≠ true) - Env1 = Env with { - global_timer = S.global_timer[A.canister_id]; - canister_version = S.canister_version[A.canister_id]; - } - Old_module.pre_upgrade(Old_State, M.caller, Env1) = Return {new_state = Intermediate_state; new_certified_data = New_certified_data; cycles_used = Cycles_used;} -) -or -( - (A.mode = upgrade U and U.skip_pre_upgrade = true) - Intermediate_state = Old_state - New_certified_data = NoCertifiedData - Cycles_used = 0 -) - -( - (A.mode = upgrade U and U.wasm_memory_persistence ≠ keep) - Persisted_state = { - wasm_memory = ""; - stable_memory = Intermediate_state.stable_memory; - globals = Mod.initial_globals; - self_id = A.canister_id; - } -) -or -( - (A.mode = upgrade U and U.wasm_memory_persistence = keep) - Persisted_state = { - wasm_memory = Intermediate_state.wasm_memory; - stable_memory = Intermediate_state.stable_memory; - globals = Mod.initial_globals; - self_id = A.canister_id; - } -) - -(A.mode = upgrade U and U.wasm_memory_persistence = keep) -or -(A.mode = upgrade U and U.wasm_memory_persistence = replace) -or -(S.canisters[A.canister_id].private_custom_sections["enhanced-orthogonal-persistence"] = null) - -not (A.mode = upgrade U and U.wasm_memory_persistence = keep and Private_custom_sections["enhanced-orthogonal-persistence"] = null) - -Env2 = Env with { - memory_usage_raw_module = memory_usage_raw_module(A.wasm_module); - memory_usage_canister_history = memory_usage_canister_history(New_canister_history); - global_timer = 0; - canister_version = S.canister_version[A.canister_id] + 1; -} - -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], 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, A.canister_id) ≥ MAX_CYCLES_PER_MESSAGE - -liquid_balance(S', A.canister_id) ≥ 0 - -(S.wasm_memory_limit[A.canister_id] = 0) or |New_state.wasm_memory| <= S.wasm_memory_limit[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 = CodeDeployment { - mode = Upgrade; - module_hash = SHA-256(A.wasm_module); - }; - }; -} -``` - -State after - -```html - -S' = S with - canisters[A.canister_id] = { - wasm_state = New_state; - module = Mod; - raw_module = A.wasm_module; - public_custom_sections = Public_custom_sections; - private_custom_sections = Private_custom_sections; - } - if New_certified_data' ≠ NoCertifiedData: - certified_data[A.canister_id] = New_certified_data' - else if New_certified_data ≠ NoCertifiedData: - certified_data[A.canister_id] = New_certified_data - if New_global_timer ≠ NoGlobalTimer: - global_timer[A.canister_id] = New_global_timer - else: - global_timer[A.canister_id] = 0 - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - balances[A.canister_id] = New_balance; - reserved_balances[A.canister_id] = New_reserved_balance; - canister_history[A.canister_id] = New_canister_history - canister_logs[A.canister_id] = S.canister_logs[A.canister_id] · canister_logs - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin; - response = Reply (candid()); - refunded_cycles = M.transferred_cycles; - } - -``` - -The logs produced by the canister during the execution of the WebAssembly `canister_pre_upgrade`, `start`, and `canister_post_upgrade` functions are modeled via the unspecified `canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[A.canister_id]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps. - -#### IC Management Canister: Install chunked code - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'install_chunked_code' -M.arg = candid(A) -if A.store_canister = null then - store_canister = A.target_canister -else - store_canister = A.store_canister -M.caller ∈ S.controllers[A.target_canister] -M.caller ∈ S.controllers[store_canister] ∪ {store_canister} -S.canister_subnet[A.target_canister] = S.canister_subnet[strorage_canister] -∀ h ∈ A.chunk_hashes_list. h ∈ dom(S.chunk_store[store_canister]) -A.chunk_hashes_list = [h1,h2,...,hk] -wasm_module = S.chunk_store[store_canister][h1] || ... || S.chunk_store[store_canister][hk] -A.wasm_module_hash = SHA-256(wasm_module) -M' = M with - method_name = 'install_code' - arg = candid(record {A.mode; A.target_canister; wasm_module; A.arg; A.sender_canister_version}) - -``` - -State after - -```html - -S with - messages = Older_messages · CallMessage M' · Younger_messages - -``` - -#### IC Management Canister: Code uninstallation {#rule-uninstall} - -Upon uninstallation, the canister is reverted to an empty canister, and all outstanding call contexts are rejected and marked as deleted. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'uninstall_code' -M.arg = candid(A) -M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] -S.canister_history[A.canister_id] = { - total_num_changes = N; - recent_changes = H; -} - -``` - -State after - -```html - -S with - canisters[A.canister_id] = EmptyCanister - certified_data[A.canister_id] = "" - chunk_store = () - canister_history[A.canister_id] = { - 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 = CodeUninstall; - }; - } - canister_logs[A.canister_id] = [] - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - global_timer[A.canister_id] = 0 - - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid()) - refunded_cycles = M.transferred_cycles - } · - [ ResponseMessage { - origin = Ctxt.origin - response = Reject (CANISTER_REJECT, ) - refunded_cycles = Ctxt.available_cycles - } - | Ctxt_id ↦ Ctxt ∈ S.call_contexts - , Ctxt.canister = A.canister_id - , Ctxt.needs_to_respond = true - ] - - for Ctxt_id ↦ Ctxt ∈ S.call_contexts: - if Ctxt.canister = A.canister_id: - call_contexts[Ctxt_id].deleted := true - call_contexts[Ctxt_id].needs_to_respond := false - call_contexts[Ctxt_id].available_cycles := 0 - -``` - -#### IC Management Canister: Stopping a canister - -The controllers of a canister can stop a canister. Stopping a canister goes through two steps. First, the status of the canister is set to `Stopping`; as explained above, a stopping canister rejects all incoming requests and continues processing outstanding responses. When a stopping canister has no more open call contexts, its status is changed to `Stopped` and a response is generated. Note that when processing responses, a stopping canister can make calls to other canisters and thus create new call contexts. In addition, a canister which is stopped or stopping will accept (and respond) to further `stop_canister` requests. - -We encode this behavior via three (types of) transitions: - -1. First, any `stop_canister` call sets the state of the canister to `Stopping`; we record in the IC state the origin (and cycles) of all `stop_canister` calls which arrive at the canister while it is stopping (or stopped). Note that every such `stop_canister` call can be rejected by the system at any time (the canister stays stopping in this case), e.g., if the `stop_canister` call could not be responded to for a long time. - -2. Next, when the canister has no open call contexts (so, in particular, all outstanding responses to the canister have been processed), the status of the canister is set to `Stopped`. - -3. Finally, each pending `stop_canister` call (which are encoded in the status) is responded to, to indicate that the canister is stopped. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'stop_canister' -M.arg = candid(A) -S.canister_status[A.canister_id] = Running -M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages - canister_status[A.canister_id] = Stopping [(M.origin, M.transferred_cycles)] - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - -``` - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'stop_canister' -M.arg = candid(A) -S.canister_status[A.canister_id] = Stopping Origins -M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages - canister_status[A.canister_id] = Stopping (Origins · [(M.origin, M.transferred_cycles)]) - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - -``` - -The status of a stopping canister which has no open call contexts is set to `Stopped`, and all pending `stop_canister` calls are replied to. - -Conditions - -```html - -S.canister_status[CanisterId] = Stopping Origins -∀ Ctxt_id. S.call_contexts[Ctxt_id].canister ≠ CanisterId - -``` - -State after - -```html - -S with - canister_status[CanisterId] = Stopped - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - messages = S.Messages · - [ ResponseMessage { - origin = O - response = Reply (candid()) - refunded_cycles = C - } - | (O, C) ∈ Origins - ] - -``` - -Sending a `stop_canister` message to an already stopped canister is acknowledged (i.e. responded with success) and the canister version is incremented, but is otherwise a no-op: - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'stop_canister' -M.arg = candid(A) -S.canister_status[A.canister_id] = Stopped -M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -State after - -```html - -S with - 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; - } - -``` - -Pending `stop_canister` calls may be rejected by the system at any time (the canister stays stopping in this case): - -Conditions - -```html - -S.canister_status[CanisterId] = Stopping (Older_origins · (O, C) · Younger_origins) - -``` - -State after - -```html - -S with - canister_status[CanisterId] = Stopping (Older_origins · Younger_origins) - messages = S.Messages · - ResponseMessage { - origin = O - response = Reject (SYS_TRANSIENT, ) - refunded_cycles = C - } - -``` - -#### IC Management Canister: Starting a canister - -The controllers of a canister can start a `stopped` canister. If the canister is already running, the command has no effect on the canister (except for incrementing its canister version). - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'start_canister' -M.arg = candid(A) -S.canister_status[A.canister_id] = Running or S.canister_status[A.canister_id] = Stopped -M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -State after - -```html - -S with - canister_status[A.canister_id] = Running - 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 - } - -``` - -If the status of the canister was 'stopping', then the canister status is set to `running`. The pending `stop_canister` request(s) are rejected. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'start_canister' -M.arg = candid(A) -S.canister_status[A.canister_id] = Stopping Origins -M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -State after - -```html - -S with - canister_status[A.canister_id] = Running - 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 - } · - [ ResponseMessage { - origin = O - response = Reject (CANISTER_ERROR, ) - refunded_cycles = C - } - | (O, C) ∈ Origins - ] - -``` - -#### IC Management Canister: Canister deletion - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'delete_canister' -M.arg = candid(A) -S.canister_status[A.canister_id] = Stopped -M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] - -``` - -State after - -```html - -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) - freezing_threshold[A.canister_id] = (deleted) - canister_status[A.canister_id] = (deleted) - canister_version[A.canister_id] = (deleted) - canister_subnet[A.canister_id] = (deleted) - time[A.canister_id] = (deleted) - global_timer[A.canister_id] = (deleted) - balances[A.canister_id] = (deleted) - reserved_balances[A.canister_id] = (deleted) - reserved_balance_limits[A.canister_id] = (deleted) - wasm_memory_limit[A.canister_id] = (deleted) - wasm_memory_threshold[A.canister_id] = (deleted) - on_low_wasm_memory_hook_status[A.canister_id] = (deleted) - certified_data[A.canister_id] = (deleted) - canister_history[A.canister_id] = (deleted) - canister_log_visibility[A.canister_id] = (deleted) - canister_snapshot_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 - response = Reply (candid()) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Depositing cycles - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'deposit_cycles' -M.arg = candid(A) -A.canister_id ∈ dom(S.balances) - -``` - -State after - -```html - -S with - balances[A.canister_id] = - S.balances[A.canister_id] + M.transferred_cycles - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid()) - refunded_cycles = 0 - } - -``` - -#### IC Management Canister: Random numbers - -The management canister can produce pseudo-random bytes. It always returns a 32-byte `blob`: - -The precise guarantees around the randomness, e.g. unpredictability, are not captured in this formal semantics. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'raw_rand' -M.arg = candid() -|B| = 32 - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid(B)) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Node Metrics - -:::note - -The node metrics management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. - -::: - -The management canister returns metrics for nodes on a given subnet. The definition of the metrics values -is not captured in this formal semantics. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'node_metrics_history' -M.arg = candid(A) -R = - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid(R)) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Subnet information - -The management canister returns subnet metadata given a subnet ID. - -Conditions - -```html -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'subnet_info' -R = -``` - -State after - -```html -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid(R)) - refunded_cycles = M.transferred_cycles - } -``` - - -#### IC Management Canister: Canister creation with cycles - -This is a variant of `create_canister`, which sets the initial cycle balance based on the `amount` argument. - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'provisional_create_canister_with_cycles' -M.arg = candid(A) -is_system_assigned Canister_id -Canister_id ∉ dom(S.canisters) -(A.settings.environment_variables = null or - (|A.settings.environment_variables| ≤ MAX_ENV_VAR_COUNT and - ∀(name, value) ∈ A.settings.environment_variables: - |name| ≤ MAX_ENV_VAR_NAME_LENGTH and - |value| ≤ MAX_ENV_VAR_VALUE_LENGTH and - is_valid_utf8(name) and - is_valid_utf8(value))) - -if A.specified_id is not null: - Canister_id = A.specified_id -if A.settings.controllers is not null: - New_controllers = A.settings.controllers -else: - New_controllers = [M.caller] - -if A.settings.compute_allocation is not null: - New_compute_allocation = A.settings.compute_allocation -else: - New_compute_allocation = 0 -if A.settings.memory_allocation is not null: - New_memory_allocation = A.settings.memory_allocation -else: - New_memory_allocation = 0 -if A.settings.freezing_threshold is not null: - New_freezing_threshold = A.settings.freezing_threshold -else: - New_freezing_threshold = 2592000 -if A.settings.reserved_cycles_limit is not null: - New_reserved_balance_limit = A.settings.reserved_cycles_limit -else: - New_reserved_balance_limit = 5_000_000_000_000 -if A.settings.wasm_memory_limit is not null: - New_wasm_memory_limit = A.settings.wasm_memory_limit -else: - New_wasm_memory_limit = 0 -if A.settings.wasm_memory_threshold is not null: - New_wasm_memory_threshold = A.settings.wasm_memory_threshold -else: - New_wasm_memory_threshold = 0 -if A.settings.environment_variables is not null: - New_environment_variables = A.settings.environment_variables -else: - New_environment_variables = [] - - -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: - New_balance = DEFAULT_PROVISIONAL_CYCLES_BALANCE - 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(S', Canister_id) ≥ 0 - -New_canister_history { - total_num_changes = 1 - recent_changes = { - timestamp_nanos = CurrentTime - canister_version = 0 - origin = change_origin(M.caller, A.sender_canister_version, M.origin) - details = Creation { - controllers = New_controllers - environment_variables_hash = if A.settings.environment_variables is not null then - opt hash_of_map(A.settings.environment_variables) - else - null - } - } -} - -if A.settings.log_visibility is not null: - New_canister_log_visibility = A.settings.log_visibility -else: - New_canister_log_visibility = Controllers - -if A.settings.snapshot_visibility is not null: - New_canister_snapshot_visibility = A.settings.snapshot_visibility -else: - New_canister_snapshot_visibility = Controllers -``` - -State after - -```html - -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 - compute_allocation[Canister_id] = New_compute_allocation - memory_allocation[Canister_id] = New_memory_allocation - freezing_threshold[Canister_id] = New_freezing_threshold - balances[Canister_id] = New_balance - reserved_balances[Canister_id] = New_reserved_balance - reserved_balance_limits[Canister_id] = New_reserved_balance_limit - wasm_memory_limit[Canister_id] = New_wasm_memory_limit - wasm_memory_threshold[Canister_id] = New_wasm_memory_threshold - environment_variables[Canister_id] = New_environment_variables - on_low_wasm_memory_hook_status[Canister_id] = ConditionNotSatisfied - certified_data[Canister_id] = "" - canister_history[Canister_id] = New_canister_history - canister_log_visibility[Canister_id] = New_canister_log_visibility - canister_snapshot_visibility[Canister_id] = New_canister_snapshot_visibility - canister_logs[Canister_id] = [] - query_stats[CanisterId] = [] - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid({canister_id = Canister_id})) - refunded_cycles = M.transferred_cycles - } - canister_status[Canister_id] = Running - canister_version[Canister_id] = 0 - canister_subnet[Canister_id] = Subnet { - subnet_id : SubnetId - subnet_size : SubnetSize - } - -``` - -#### IC Management Canister: Top up canister - -Conditions - -```html - -S.messages = Older_messages · CallMessage M · Younger_messages -(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) -M.callee = ic_principal -M.method_name = 'provisional_top_up_canister' -M.arg = candid(A) -A.canister_id ∈ dom(S.canisters) - -``` - -State after - -```html - -S with - balances[A.canister_id] = S.balances[A.canister_id] + A.amount - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid()) - refunded_cycles = M.transferred_cycles - } - -``` - -#### 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: - A.replace_snapshot ∈ dom(S.snapshots[A.canister_id]) -else: - |dom(S.snapshots[A.canister_id])| < MAX_SNAPSHOTS - -A.uninstall_code = null or A.uninstall_code = false - -New_snapshot = Snapshot { - source = TakenFromCanister; - take_at_timestamp = S.time[A.canister_id]; - raw_module = S.canisters[A.canister_id].raw_module; - wasm_state = S.canisters[A.canister_id].wasm_state; - chunk_store = S.chunk_store[A.canister_id]; - canister_version = S.canister_version[A.canister_id]; - certified_data = S.certified_data[A.canister_id]; - global_timer = S.global_timer[A.canister_id]; - on_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[A.canister_id]; -} -New_snapshots = S.snapshots[A.canister_id] with - A.replace_snapshot = (undefined) - Snapshot_id = New_snapshot -Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, 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_snapshots - 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_snapshots([Snapshot_id → New_snapshot]); - })); - refunded_cycles = M.transferred_cycles; - } - -``` - -It is also possible to atomically uninstall code after taking a snapshot; in particular, the canister memory usage is updated atomically and thus it does not grow significantly (ignoring some potential constant overhead for certified variables which are not accounted for by canister memory usage, but are accounted for in canister snapshot memory usage). - -```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] -S.canister_history[A.canister_id] = { - total_num_changes = N; - recent_changes = H; -} - -if A.replace_snapshot is not null: - A.replace_snapshot ∈ dom(S.snapshots[A.canister_id]) -else: - |dom(S.snapshots[A.canister_id])| < MAX_SNAPSHOTS - -A.uninstall_code = true - -New_snapshot = Snapshot { - source = TakenFromCanister; - take_at_timestamp = S.time[A.canister_id]; - raw_module = S.canisters[A.canister_id].raw_module; - wasm_state = S.canisters[A.canister_id].wasm_state; - chunk_store = S.chunk_store[A.canister_id]; - canister_version = S.canister_version[A.canister_id]; - certified_data = S.certified_data[A.canister_id]; - global_timer = S.global_timer[A.canister_id]; - on_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[A.canister_id]; -} -New_snapshots = S.snapshots[A.canister_id] with - A.replace_snapshot = (undefined) - Snapshot_id = New_snapshot -Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, EmptyCanister) -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_snapshots - balances[A.canister_id] = New_balance - reserved_balances[A.canister_id] = New_reserved_balance - - canisters[A.canister_id] = EmptyCanister - certified_data[A.canister_id] = "" - chunk_store = () - canister_history[A.canister_id] = { - 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 = CodeUninstall; - }; - } - canister_logs[A.canister_id] = [] - canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 - global_timer[A.canister_id] = 0 - - 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_snapshots([Snapshot_id → New_snapshot]); - })); - refunded_cycles = M.transferred_cycles; - } · - [ ResponseMessage { - origin = Ctxt.origin - response = Reject (CANISTER_REJECT, ) - refunded_cycles = Ctxt.available_cycles - } - | Ctxt_id ↦ Ctxt ∈ S.call_contexts - , Ctxt.canister = A.canister_id - , Ctxt.needs_to_respond = true - ] - - for Ctxt_id ↦ Ctxt ∈ S.call_contexts: - if Ctxt.canister = A.canister_id: - call_contexts[Ctxt_id].deleted := true - call_contexts[Ctxt_id].needs_to_respond := false - call_contexts[Ctxt_id].available_cycles := 0 - -``` - -#### IC Management Canister: Load canister snapshot - - -Controllers of a canister can load a snapshot that belongs to a canister on the same subnet and also controlled by the caller. - -```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] -A.snapshot_id ∈ dom(S.snapshots[Canister_id]) -S.canister_subnet[A.canister_id].subnet_id = S.canister_subnet[Canister_id].subnet_id -M.caller ∈ S.controllers[Canister_id] -Snapshot = S.snapshots[Canister_id][A.snapshot_id] - -Mod = parse_wasm_mod(Snapshot.raw_module); - -|Snapshot.wasm_state.globals| = |Mod.initial_globals| -for i in [0..|Snapshot.wasm_state.globals|]: - if Snapshot.wasm_state.globals[i] = I32(_): - Mod.initial_globals = I32(_) - else if Snapshot.wasm_state.globals[i] = I64(_): - Mod.initial_globals = I64(_) - else if Snapshot.wasm_state.globals[i] = F32(_): - Mod.initial_globals = F32(_) - else if Snapshot.wasm_state.globals[i] = F64(_): - Mod.initial_globals = F64(_) - else if Snapshot.wasm_state.globals[i] = V128(_): - Mod.initial_globals = V128(_) - -if Snapshot.source = MetadataUpload: - if Snapshot.on_low_wasm_memory_hook_status = ConditionNotSatisfied: - HookConditionInSnapshotField = false - else: - HookConditionInSnapshotField = true - if S.wasm_memory_limit[A.canister_id] < |Snapshot.wasm_state.wasm_memory| + S.wasm_memory_threshold[A.canister_id]: - HookConditionInSnapshotState = true - else: - HookConditionInSnapshotState = false - (HookConditionInSnapshotField and HookConditionInSnapshotState) - or - ((not HookConditionInSnapshotField) and (not HookConditionInSnapshotState)) - -New_state = { - wasm_state = Snapshot.wasm_state; - raw_module = Snapshot.raw_module; - module = Mod; - public_custom_sections = parse_public_custom_sections(Snapshot.raw_module); - private_custom_sections = parse_private_custom_sections(Snapshot.raw_module); -} - -if Snapshot.source = MetadataUpload and Snapshot.global_timer is not null: - New_global_timer = Snapshot.global_timer -else: - New_global_timer = S.global_timer[A.canister_id] - -if Snapshot.source = MetadataUpload and Snapshot.on_low_wasm_memory_hook_status is not null: - New_on_low_wasm_memory_hook_status = Snapshot.on_low_wasm_memory_hook_status -else: - New_on_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[A.canister_id] - -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; -} -if Canister_id = A.canister_id: - From_canister_id = null -else: - From_canister_id = Canister_id -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 { - from_canister_id = From_canister_id - snapshot_id = A.snapshot_id - canister_version = Snapshot.canister_version - taken_at_timestamp = Snapshot.take_at_timestamp - source = Snapshot.source - }; - }; -} - -liquid_balance(S', A.canister_id) ≥ 0 - -``` - -State after - -```html - -S' = S with - canisters[A.canister_id] = New_state - chunk_store[A.canister_id] = Snapshot.chunk_store - certified_data[A.canister_id] = Snapshot.certified_data - global_timer[A.canister_id] = New_global_timer - on_low_wasm_memory_hook_status[A.canister_id] = New_on_low_wasm_memory_hook_status - 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: Read snapshot metadata - -Access to the metadata of a canister snapshot is determined by the canister settings `canister_snapshot_visibility`. - -```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 = 'read_canister_snapshot_metadata' -M.arg = candid(A) -(S[A.canister_id].canister_snapshot_visibility = Public) - or - (S[A.canister_id].canister_snapshot_visibility = Controllers and M.caller in S[A.canister_id].controllers) - or - (S[A.canister_id].canister_snapshot_visibility = AllowedViewers Principals and (M.caller in S[A.canister_id].controllers or M.caller in Principals)) - -A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) -Snapshot = S.snapshots[A.canister_id][A.snapshot_id] - -SnapshotMetadata = { - source = Snapshot.source; - taken_at_timestamp = Snapshot.taken_at_timestamp; - wasm_module_size = |Snapshot.raw_module|; - globals = Snapshot.wasm_state.globals; - wasm_memory_size = |Snapshot.wasm_state.wasm_memory|; - stable_memory_size = |Snapshot.wasm_state.stable_memory|; - wasm_chunk_store = [{hash: hash} | hash <- dom(Snapshot.chunk_store)] - canister_version = Snapshot.canister_version; - certified_data = Snapshot.certified_data; - global_timer = Snapshot.global_timer; - on_low_wasm_memory_hook_status = Snapshot.on_low_wasm_memory_hook_status; -} - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid(SnapshotMetadata)) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Read snapshot data - -Access to the (binary) data of a canister snapshot is determined by the canister settings `canister_snapshot_visibility`. - -```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 = 'read_canister_snapshot_data' -M.arg = candid(A) -(S[A.canister_id].canister_snapshot_visibility = Public) - or - (S[A.canister_id].canister_snapshot_visibility = Controllers and M.caller in S[A.canister_id].controllers) - or - (S[A.canister_id].canister_snapshot_visibility = AllowedViewers Principals and (M.caller in S[A.canister_id].controllers or M.caller in Principals)) - -A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) -Snapshot = S.snapshots[A.canister_id][A.snapshot_id] - -if A.kind = WasmModule { offset, size }: - offset + size <= |Snapshot.raw_module| -else if A.kind = WasmMemory { offset, size }: - offset + size <= |Snapshot.wasm_state.wasm_memory| -else if A.kind = StableMemory { offset, size }: - offset + size <= |Snapshot.wasm_state.stable_memory| -else if A.kind = WasmChunk { hash }: - hash in dom(Snapshot.chunk_store) - -if A.kind = WasmModule { offset, size }: - Chunk = Snapshot.raw_module[offset..offset+size] -else if A.kind = WasmMemory { offset, size }: - Chunk = Snapshot.wasm_state.wasm_memory[offset..offset+size] -else if A.kind = StableMemory { offset, size }: - Chunk = Snapshot.wasm_state.stable_memory[offset..offset+size] -else if A.kind = WasmChunk { hash }: - Chunk = Snapshot.chunk_store[hash] - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages · - ResponseMessage { - origin = M.origin - response = Reply (candid({chunk = Chunk})) - refunded_cycles = M.transferred_cycles - } - -``` - -#### IC Management Canister: Upload canister snapshot metadata - -Only the controllers of the given canister can create a new snapshot by uploading its metadata. -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 = 'upload_canister_snapshot_metadata' -M.arg = candid(A) -M.caller ∈ S.controllers[A.canister_id] -if A.replace_snapshot is not null: - A.replace_snapshot ∈ dom(S.snapshots[A.canister_id]) -else: - |dom(S.snapshots[A.canister_id])| < MAX_SNAPSHOTS - -New_snapshot = Snapshot { - source = MetadataUpload; - take_at_timestamp = S.time[A.canister_id]; - raw_module = [0 | _ <- [0..A.wasm_module_size]]; - wasm_state = { - wasm_memory = [0 | _ <- [0..A.wasm_memory_size]]; - stable_memory = [0 | _ <- [0..A.stable_memory_size]]; - globals = A.globals; - self_id = A.canister_id; - }; - chunk_store = []; - canister_version = S.canister_version[A.canister_id]; - certified_data = A.certified_data; - global_timer = A.global_timer; - on_low_wasm_memory_hook_status = A.on_low_wasm_memory_hook_status; -} -New_snapshots = S.snapshots[A.canister_id] with - A.replace_snapshot = (undefined) - Snapshot_id = New_snapshot -Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, 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_snapshots - 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({ - snapshot_id = Snapshot_id; - })); - refunded_cycles = M.transferred_cycles; - } - -``` - -#### IC Management Canister: Upload canister snapshot data - -Only the controllers of the given canister can upload (binary) data to its 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 = 'upload_canister_snapshot_data' -M.arg = candid(A) -M.caller ∈ S.controllers[A.canister_id] - -A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) -Snapshot = S.snapshots[A.canister_id][A.snapshot_id] -Snapshot.source = MetadataUpload - -if A.kind = WasmModule { offset }: - offset + |A.chunk| <= |Snapshot.raw_module| -else if A.kind = WasmMemory { offset }: - offset + |A.chunk| <= |Snapshot.wasm_state.wasm_memory| -else if A.kind = StableMemory { offset }: - offset + |A.chunk| <= |Snapshot.wasm_state.stable_memory| -else if A.kind = WasmChunk { hash }: - |dom(Snapshot.chunk_store) ∪ {SHA-256(A.chunk)}| <= CHUNK_STORE_SIZE - -if A.kind = WasmModule { offset }: - New_raw_module = Snapshot.raw_module[0..offset] · A.chunk · Snapshot.raw_module[offset+|A.chunk|..|Snapshot.raw_module|] - New_snapshot = Snapshot with - raw_module = New_raw_module -else if A.kind = WasmMemory { offset }: - New_wasm_memory = Snapshot.wasm_memory[0..offset] · A.chunk · Snapshot.wasm_memory[offset+|A.chunk|..|Snapshot.wasm_memory|] - New_snapshot = Snapshot with - wasm_memory = New_wasm_memory -else if A.kind = StableMemory { offset }: - New_stable_memory = Snapshot.stable_memory[0..offset] · A.chunk · Snapshot.stable_memory[offset+|A.chunk|..|Snapshot.stable_memory|] - New_snapshot = Snapshot with - stable_memory = New_stable_memory -else if A.kind = WasmChunk: - New_chunk_store = Snapshot.chunk_store with - SHA-256(A.chunk) = A.chunk - New_snapshot = Snapshot with - chunk_store = New_chunk_store - -New_snapshots = S.snapshots[A.canister_id] with - Snapshot_id = New_snapshot - -Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, 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_snapshots - 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()); - refunded_cycles = M.transferred_cycles; - } - -``` - -#### IC Management Canister: List canister snapshots - -Access to the list of the existing snapshots of a canister is determined by the canister settings `canister_snapshot_visibility`. - -```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) -(S[A.canister_id].canister_snapshot_visibility = Public) - or - (S[A.canister_id].canister_snapshot_visibility = Controllers and M.caller in S[A.canister_id].controllers) - or - (S[A.canister_id].canister_snapshot_visibility = AllowedViewers Principals and (M.caller in S[A.canister_id].controllers or M.caller in Principals)) - - -Snapshots = [{ - id = Snapshot_id; - taken_at_timestamp = Snapshot.taken_at_timestamp; - total_size = memory_usage_snapshots([Snapshot_id → Snapshot]); -} | Snapshot_id → 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 that the snapshot belongs to. - -```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] -A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) - -``` - -State after - -```html - -S with - S.snapshots[A.canister_id][A.snapshot_id] = (deleted) - 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. - -This "bookkeeping transition" must be immediately followed by the corresponding ["Message execution" transition](#rule-message-execution). - -Conditions - -```html - -S.messages = Older_messages · ResponseMessage RM · Younger_messages -RM.origin = FromCanister { - call_context = Ctxt_id - callback = Callback - deadline = D - } -Ctxt_id ∈ dom(S.call_contexts) -Ctxt = S.call_contexts[Ctxt_id] -not Ctxt.deleted -Ctxt.canister ∈ dom(S.balances) -D ≠ Expired _ - -Caller = if Ctxt.origin = FromUser { request = R }: - R.sender -else if Ctxt.origin = FromCanister { calling_context = Calling_ctxt, …}: - S.call_contexts[Calling_ctxt].canister -else: - ic_principal - -if Ctxt.origin = FromUser { request = R }: - if R.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): - if R.sender_info = null: - Caller_info_data = "" - Caller_info_signer = "" - else: - Caller_info_data = R.sender_info.info - Caller_info_signer = Signing_canister_id - else: - Caller_info_data = "" - Caller_info_signer = "" -else: - Caller_info_data = "" - Caller_info_signer = "" - -``` - -State after - -```html - -S with - balances[S.call_contexts[Ctxt_id].canister] = - S.balances[S.call_contexts[Ctxt_id].canister] + RM.refunded_cycles - messages = - Older_messages · - FuncMessage { - call_context = Ctxt_id - caller = Caller - caller_info_data = Caller_info_data - caller_info_signer = Caller_info_signer - receiver = S.call_contexts[Ctxt_id].canister - entry_point = Callback Callback RM.response RM.refunded_cycles - queue = Unordered - } · - Younger_messages - -``` - -If the responded call context does not exist anymore, because the canister has been uninstalled since, the refunded cycles are still added to the canister balance, but no function invocation is enqueued. - -Conditions - -```html - -S.messages = Older_messages · ResponseMessage RM · Younger_messages -RM.origin = FromCanister { - call_context = Ctxt_id - callback = Callback - deadline = D - } -Ctxt_id ∈ dom(S.call_contexts) -S.call_contexts[Ctxt_id].deleted -S.call_contexts[Ctxt_id].canister ∈ dom(S.balances) -D ≠ Expired _ - -``` - -State after - -```html - -S with - balances[S.call_contexts[Ctxt_id].canister] = - S.balances[S.call_contexts[Ctxt_id].canister] + RM.refunded_cycles + MAX_CYCLES_PER_RESPONSE - messages = Older_messages · Younger_messages - -``` - -#### Dropping expired messages {#message-timeout} - -Condition: -```html -S.messages = Older_messages · M · Younger_messages -M = ResponseMessage _ ∨ M = CallMessage _ -M.origin = FromCanister O -O.deadline = Expired _ -``` - -State after - -```html -S.messages = Older_messages · Younger_messages -``` - -#### Respond to user request - -When an ingress method call has been responded to, we can record the response in the list of queries. - -Conditions - -```html - -S.messages = Older_messages · ResponseMessage RM · Younger_messages -RM.origin = FromUser { request = M } -S.requests[M] = (Processing, ECID) - -``` - -State after - -```html - -S with - messages = Older_messages · Younger_messages - requests[M] = - | (Replied R, ECID) if M.response = Reply R - | (Rejected (c, R), ECID) if M.response = Reject (c, R) - -``` - -NB: The refunded cycles, `RM.refunded_cycles` are, by construction, empty. - -#### Update call request clean up - -The IC will keep the data for a replied or rejected update `call` request around for a certain, implementation defined amount of time, to allow users to poll for the data with `read_state` requests . After that time, the data of the request will be dropped: - -Conditions - -```html - -(S.requests[M] = (Replied _, ECID)) or (S.requests[M] = (Rejected _, ECID)) - -``` - -State after - -```html - -S with - requests[M] = (Done, ECID) - -``` - -At the same or some later point, the request will be removed from the state of the IC. This must happen no earlier than the ingress expiry time set in the request. - -Conditions - -```html - -(S.requests[M] = (Replied _, _)) or (S.requests[M] = (Rejected _, _)) or (S.requests[M] = (Done, _)) -M.ingress_expiry < S.system_time - -``` - -State after - -```html - -S with - requests[M] = (deleted) - -``` - -#### Canister out of cycles - -Once a canister runs out of cycles, its code is uninstalled (cf. [IC Management Canister: Code uninstallation](#rule-uninstall)), the canister changes in the canister history are dropped (their total number is preserved), and the allocations are set to zero: - -Conditions - -```html - -S.balances[CanisterId] = 0 -S.reserved_balances[CanisterId] = 0 -S.canister_history[CanisterId] = { - total_num_changes = N; - recent_changes = H; -} - -``` - -State after - -```html - -S with - canisters[CanisterId] = EmptyCanister - snapshots[CanisterId] = null - certified_data[CanisterId] = "" - canister_history[CanisterId] = { - total_num_changes = N; - recent_changes = []; - } - canister_logs[CanisterId] = [] - canister_version[CanisterId] = S.canister_version[CanisterId] + 1 - global_timer[CanisterId] = 0 - compute_allocation[Canister_id] = 0 - memory_allocation[Canister_id] = 0 - - messages = S.messages · - [ ResponseMessage { - origin = Ctxt.origin - response = Reject (CANISTER_REJECT, ) - refunded_cycles = Ctxt.available_cycles - } - | Ctxt_id ↦ Ctxt ∈ S.call_contexts - , Ctxt.canister = CanisterId - , Ctxt.needs_to_respond = true - ] - - for Ctxt_id ↦ Ctxt ∈ S.call_contexts: - if Ctxt.canister = CanisterId: - call_contexts[Ctxt_id].deleted := true - call_contexts[Ctxt_id].needs_to_respond := false - call_contexts[Ctxt_id].available_cycles := 0 - -``` - -#### Canister renaming - -The system canister migration orchestrator (we denote its canister ID by `Canister_migration_orchestrator`) -can perform canister renaming of a canister with canister ID `Canister_id` -to a new canister ID `New_canister_id`. -The actual caller of the corresponding canister migration orchestrator's endpoint who requested canister renaming is denoted by `Caller`. -We denote the system canister migration orchestrator version when performing the renaming by `Canister_migration_orchestrator_version`. - -Conditions - -```html - -not (S.canister_subnet[Canister_id] = S.canister_subnet[New_canister_id]) - -Caller ∈ S.controllers[Canister_id] -Caller ∈ S.controllers[New_canister_id] - -Canister_migration_orchestrator ∈ S.controllers[Canister_id] -Canister_migration_orchestrator ∈ S.controllers[New_canister_id] - -S.canister_status[Canister_id] = Stopped -S.canister_status[New_canister_id] = Stopped - -∀ Snapshot_id. S.snapshots[Canister_id][Snapshot_id] = null - -S.canister_history[Canister_id] = { - total_num_changes = N; - recent_changes = H; -} - -New_canister_history = { - total_num_changes = S.canister_history[New_canister_id].total_num_changes + 1; - recent_changes = H · { - timestamp_nanos = S.time[Canister_id]; - canister_version = max(S.canister_version[New_canister_id], S.canister_version[Canister_id]) + 1; - origin = FromCanister { - canister_id = Canister_migration_orchestrator - canister_version = Canister_migration_orchestrator_version - }; - details = RenameCanister { - canister_id = Canister_id; - total_num_changes = N; - rename_to = { - canister_id = New_canister_id; - version = S.canister_version[New_canister_id]; - total_num_changes = S.canister_history[New_canister_id].total_num_changes; - }; - requested_by = Caller; - }; - }; -} - -``` - -State after - -```html - -S with - canisters[New_canister_id] = S.canisters[Canister_id] - canisters[Canister_id] = (deleted) - snapshots[New_canister_id] = {} - snapshots[Canister_id] = (deleted) - controllers[New_canister_id] = S.controllers[Canister_id] - controllers[Canister_id] = (deleted) - compute_allocation[New_canister_id] = S.compute_allocation[Canister_id] - compute_allocation[Canister_id] = (deleted) - memory_allocation[New_canister_id] = S.memory_allocation[Canister_id] - memory_allocation[Canister_id] = (deleted) - freezing_threshold[New_canister_id] = S.freezing_threshold[Canister_id] - freezing_threshold[Canister_id] = (deleted) - canister_status[New_canister_id] = S.canister_status[Canister_id] - canister_status[Canister_id] = (deleted) - canister_version[New_canister_id] = max(S.canister_version[New_canister_id], S.canister_version[Canister_id]) + 1 - canister_version[Canister_id] = (deleted) - canister_subnet[New_canister_id] = S.canister_subnet[Canister_id] - canister_subnet[Canister_id] = (deleted) - time[New_canister_id] = S.time[Canister_id] - time[Canister_id] = (deleted) - global_timer[New_canister_id] = S.global_timer[Canister_id] - global_timer[Canister_id] = (deleted) - balances[New_canister_id] = S.balances[Canister_id] - balances[Canister_id] = (deleted) - reserved_balances[New_canister_id] = S.reserved_balances[Canister_id] - reserved_balances[Canister_id] = (deleted) - reserved_balance_limits[New_canister_id] = S.reserved_balance_limits[Canister_id] - reserved_balance_limits[Canister_id] = (deleted) - wasm_memory_limit[New_canister_id] = S.wasm_memory_limit[Canister_id] - wasm_memory_limit[Canister_id] = (deleted) - wasm_memory_threshold[New_canister_id] = S.wasm_memory_threshold[Canister_id] - wasm_memory_threshold[Canister_id] = (deleted) - environment_variables[New_canister_id] = S.environment_variables[Canister_id] - environment_variables[Canister_id] = (deleted) - on_low_wasm_memory_hook_status[New_canister_id] = S.on_low_wasm_memory_hook_status[Canister_id] - on_low_wasm_memory_hook_status[Canister_id] = (deleted) - certified_data[New_canister_id] = S.certified_data[Canister_id] - certified_data[Canister_id] = (deleted) - canister_history[New_canister_id] = New_canister_history - canister_history[Canister_id] = (deleted) - canister_log_visibility[New_canister_id] = S.canister_log_visibility[Canister_id] - canister_log_visibility[Canister_id] = (deleted) - canister_snapshot_visibility[New_canister_id] = S.canister_snapshot_visibility[Canister_id] - canister_snapshot_visibility[Canister_id] = (deleted) - canister_logs[New_canister_id] = S.canister_logs[Canister_id] - canister_logs[Canister_id] = (deleted) - query_stats[New_canister_id] = S.query_stats[Canister_id] - query_stats[Canister_id] = (deleted) - -``` - -#### Time progressing, cycle consumption, canister version increments and subnet admins updates - -Time progresses. Abstractly, it does so independently for each canister, and in unspecified intervals. - -Conditions - -```html - -T0 = S.time[CanisterId] -T1 > T0 - -``` - -State after - -```html - -S with - time[CanisterId] = T1 - -``` - -The canister cycle balances similarly deplete at an unspecified rate, but stay non-negative. -If the canister has a positive reserved balance, then the reserved balance depletes before the main balance: - -Conditions - -```html -R0 = S.reserved_balances[CanisterId] -0 ≤ R1 < R0 - -``` - -State after - -```html - -S with - reserved_balances[CanisterId] = R1 - -``` - -Once the reserved balance reaches zero, then the main balance starts depleting: - -Conditions - -```html -S.reserved_balances[CanisterId] = 0 -B0 = S.balances[CanisterId] -0 ≤ B1 < B0 - -``` - -State after - -```html - -S with - balances[CanisterId] = B1 - -``` - -Similarly, the system time, used to expire requests, progresses: - -Conditions - -```html - -T0 = S.system_time -T1 > T0 - -``` - -State after - -```html - -S with - system_time = T1 - -``` - -Additionally, the canister version can be incremented arbitrarily: - -Conditions - -```html - -N0 = S.canister_version[CanisterId] -N1 > N0 - -``` - -State after - -```html - -S with - canister_version[CanisterId] = N1 - -``` - -Finally, subnet admins can be changed arbirtrarily: - -Conditions - -```html - -SA0 = S.subnet_admins -SA1 != SA0 - -``` - -State after - -```html - -S with - subnet_admins = SA1 - -``` - -:::note - -In production, subnet admins can be set via the Subnet Rental Canister which is not modeled in this document. - -#### Trimming canister history - -The list of canister changes can be trimmed, but the total number of recorded canister changes cannot be altered. At least 20 changes are guaranteed to remain in the list of changes. - -Conditions - -```html - -S.canister_history[CanisterId] = { - total_num_changes = N; - recent_changes = Older_changes · Newer_changes; - } -|Newer_changes| ≥ 20 - -``` - -State after - -```html - -S with - canister_history[CanisterId] = { - total_num_changes = N; - recent_changes = Newer_changes; - } - -``` - -#### Trimming canister logs - -Canister logs can be trimmed if their total length exceeds 4KiB. - -Conditions - -```html - -S.canister_logs[CanisterId] = Older_logs · Newer_logs -SUM { |l| | l <- Older_logs } > 4KiB - -``` - -State after - -```html - -S with - canister_logs[CanisterId] = Newer_logs - -``` - -#### IC Management Canister: Canister logs (query call) {#ic-mgmt-canister-fetch-canister-logs} - -This section specifies management canister query calls. -They are calls to `/api/v3/canister//query` -with CBOR content `Q` such that `Q.canister_id = ic_principal`. - -The management canister offers the method `fetch_canister_logs` -that can be called as a query call and -returns logs of a requested canister. - -Submitted request to `/api/v3/canister//query` - -```html - -E : Envelope - -``` - -Conditions - -```html - -E.content = CanisterQuery Q -Q.canister_id = ic_principal -Q.method_name = 'fetch_canister_logs' -|Q.nonce| <= 32 -is_effective_canister_id(E.content, ECID) -S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id -Q.arg = candid(A) -A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) -if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: - if not (Q.sender_info = null): - verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) - Q.sender_info.signer = Signing_canister_id -else: - Q.sender_info = null -(S[A.canister_id].canister_log_visibility = Public) - or - (S[A.canister_id].canister_log_visibility = Controllers and Q.sender in S[A.canister_id].controllers) - or - (S[A.canister_id].canister_log_visibility = AllowedViewers Principals and (Q.sender in S[A.canister_id].controllers or Q.sender in Principals)) - -``` - -Query response `R`: - -```html - -{status: "replied"; reply: {arg: candid(S.canister_logs[A.canister_id])}, signatures: Sigs} - -``` - -where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: - -```html - -verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" - -``` - -#### IC Management Canister: List canisters (query call) {#ic-mgmt-canister-list-canisters} - -This section specifies the `list_canisters` management canister query call. -It is a call to `/api/v3/canister//query` -with CBOR content `Q` such that `Q.canister_id = ic_principal`. - -The management canister offers the method `list_canisters` -that can be called as a query call by subnet admins and -returns the list of all canisters on the subnet as consecutive canister ID ranges. - -Submitted request to `/api/v3/canister//query` - -```html - -E : Envelope - -``` - -Conditions - -```html - -E.content = CanisterQuery Q -Q.canister_id = ic_principal -Q.method_name = 'list_canisters' -|Q.nonce| <= 32 -is_effective_canister_id(E.content, ECID) -S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id -verify_envelope(E, Q.sender, S.system_time) -Q.sender ∈ S.subnet_admins[S.canister_subnet[ECID]] - -``` - -Query response `R`: - -```html - -{status: "replied"; reply: {arg: candid({canisters: CanisterIdRanges})}, signatures: Sigs} - -``` - -where `CanisterIdRanges` is the list of all canister IDs on the subnet encoded as consecutive canister ID ranges (excluding deleted canisters), and the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: - -```html - -verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" - -``` - -#### Query call {#query-call} - -This section specifies query calls `Q` whose `Q.canister_id` is a non-empty canister `S.canisters[Q.canister_id]`. Query calls to the management canister, i.e., `Q.canister_id = ic_principal`, are specified in Sections [Canister status](#ic-management-canister-canister-status), [Canister logs](#ic-mgmt-canister-fetch-canister-logs), and [List canisters](#ic-mgmt-canister-list-canisters). - -Canister query calls to `/api/v3/canister//query` can be executed directly. They can only be executed against non-empty canisters which have a status of `Running` and are also not frozen. - -In query and composite query methods evaluated on the target canister of the query call, a certificate is provided to the canister that is valid, contains a current state tree (or "recent enough"; the specification is currently vague about how old the certificate may be), and reveals the canister's [Certified Data](#system-api-certified-data). - -Composite query methods can call query methods and composite query methods up to a maximum depth `MAX_CALL_DEPTH_COMPOSITE_QUERY` of the call graph. The total amount of cycles consumed by executing a (composite) query method and all (transitive) calls it makes must be at most `MAX_CYCLES_PER_QUERY`. This limit applies in addition to the limit `MAX_CYCLES_PER_MESSAGE` for executing a single (composite) query method and `MAX_CYCLES_PER_RESPONSE` for executing a single callback of a (composite) query method. - -We define an auxiliary method that handles calls from composite query methods by performing a call graph traversal. It can also be (trivially) invoked for query methods that do not make further calls. -``` -composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Caller_info_data, Caller_info_signer, Canister_id, Method_name, Arg) = - let Mod = S.canisters[Canister_id].module - let Cert <- { Cert | verify_cert(Cert) and - lookup(["canister", Canister_id, "certified_data"], Cert) = Found S.certified_data[Canister_id] and - lookup(["time"], Cert) = Found S.system_time // or "recent enough" - } - if Canister_id ≠ Root_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]; - reserved_balance_limit = S.reserved_balance_limits[Canister_id]; - compute_allocation = S.compute_allocation[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_snapshots = memory_usage_snapshots(S.snapshots[Canister_id]); - freezing_threshold = S.freezing_threshold[Canister_id]; - subnet_id = S.canister_subnet[Canister_id].subnet_id; - subnet_size = S.canister_subnet[Canister_id].subnet_size; - certificate = Cert; - status = simple_status(S.canister_status[Canister_id]); - canister_version = S.canister_version[Canister_id]; - } - if S.canisters[Canister_id] ≠ EmptyCanister and - S.canister_status[Canister_id] = Running and - (Method_name ∈ dom(Mod.query_methods) or Method_name ∈ dom(Mod.composite_query_methods)) and - Cycles >= MAX_CYCLES_PER_MESSAGE - 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, Canister_id) < 0 - then - Return (Reject (SYS_TRANSIENT, ), Cycles, S) - let R = F(Arg, Caller, Caller_info_data, Caller_info_signer, Env)(W) - if R = Trap trap - then Return (Reject (CANISTER_ERROR, ), Cycles - trap.cycles_used, S) - else if R = Return {new_state = W'; new_calls = Calls; response = Response; cycles_used = Cycles_used} - then - W := W' - if Cycles_used > MAX_CYCLES_PER_MESSAGE - then - Return (Reject (CANISTER_ERROR, ), Cycles - MAX_CYCLES_PER_MESSAGE, S) // single message execution out of cycles - Cycles := Cycles - Cycles_used - if Response = NoResponse - then - while Calls ≠ [] - do - if Depth = MAX_CALL_DEPTH_COMPOSITE_QUERY - then - Return (Reject (CANISTER_ERROR, ), Cycles, S) // max call graph depth exceeded - let Calls' · Call · Calls'' = Calls - Calls := Calls' · Calls'' - if S.canister_subnet[Canister_id].subnet_id ≠ S.canister_subnet[Call.callee].subnet_id - then - Return (Reject (CANISTER_ERROR, ), Cycles, S) // calling to another subnet - let (Response', Cycles', S') = composite_query_helper(S, Cycles, Depth + 1, Root_canister_id, Canister_id, "", "", Call.callee, Call.method_name, Call.arg) - Cycles := Cycles' - S := S' - if Cycles < MAX_CYCLES_PER_RESPONSE - then - Return (Reject (CANISTER_ERROR, ), Cycles, S) // composite query out of cycles - Env.Cert = NoCertificate // no certificate available in composite query callbacks - let F' = Mod.composite_callbacks(Call.callback, Caller, Caller_info_data, Caller_info_signer, Response', Env) - let R'' = F'(W') - if R'' = Trap trap'' - then Return (Reject (CANISTER_ERROR, ), Cycles - trap''.cycles_used, S) - else if R'' = Return {new_state = W''; new_calls = Calls''; response = Response''; cycles_used = Cycles_used''} - then - W := W'' - if Cycles_used'' > MAX_CYCLES_PER_RESPONSE - then - Return (Reject (CANISTER_ERROR, ), Cycles - MAX_CYCLES_PER_RESPONSE, S) // single message execution out of cycles - Cycles := Cycles - Cycles_used'' - if Response'' = NoResponse - then - Calls := Calls'' · Calls - else - Return (Response'', Cycles, S) - Return (Reject (CANISTER_ERROR, ), Cycles, S) // canister did not respond - else - Return (Response, Cycles, S) - else - Return (Reject (CANISTER_ERROR, ), Cycles, S) -``` - -Submitted request to `/api/v3/canister//query` - -```html - -E : Envelope - -``` - -Conditions - -```html - -E.content = CanisterQuery Q -Q.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) -if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: - if not (Q.sender_info = null): - verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) - Q.sender_info.signer = Signing_canister_id -else: - Q.sender_info = null -|Q.nonce| <= 32 -is_effective_canister_id(E.content, ECID) -S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id - -if Q.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): - if Q.sender_info = null: - Caller_info_data = "" - Caller_info_signer = "" - else: - Caller_info_data = Q.sender_info.info - Caller_info_signer = Signing_canister_id -else: - Caller_info_data = "" - Caller_info_signer = "" - -``` - -Query response `R`: - -- if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Caller_info_data, Caller_info_signer, Q.canister_id, Q.method_name, Q.arg) = (Reject (RejectCode, RejectMsg), _, S')` then - ``` - {status: "rejected"; reject_code: RejectCode; reject_message: RejectMsg; error_code: , signatures: Sigs} - ``` - -- Else if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Caller_info_data, Caller_info_signer, Q.canister_id, Q.method_name, Q.arg) = (Reply Res, _, S')` then - ``` - {status: "replied"; reply: {arg: Res}, signatures: Sigs} - ``` - -where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: - -```html - -verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" - -``` - -State after - -```html - -S' with - query_stats[Q.receiver] = S'.query_stats[Q.receiver] · { - timestamp = S'.time[Q.receiver] - num_instructions = - request_payload_bytes = |Q.Arg| - response_payload_bytes = - if R.status = "rejected" then |R.reject_message| - else |R.reply.arg| - } - -``` - -#### Certified state reads - -:::note - -Requesting paths with the prefix `/subnet` at `/api/v3/canister//read_state` might be deprecated in the future. Hence, users might want to point their requests for paths with the prefix `/subnet` to `/api/v3/subnet//read_state`. - -On the IC mainnet, the root subnet ID `tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe` can be used to retrieve the list of all IC mainnet's subnets by requesting the prefix `/subnet` at `/api/v3/subnet/tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe/read_state`. - -::: - -The user can read elements of the *state tree*, using a `read_state` request to `/api/v3/canister//read_state` or `/api/v3/subnet//read_state`. - -Submitted request to `/api/v3/canister//read_state` - -```html - -E : Envelope - -``` - -Conditions - -```html - -E.content = ReadState RS -TS = verify_envelope(E, RS.sender, S.system_time) -|E.content.nonce| <= 32 -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 - -``` - -Read response -A record with - -- `{certificate: C}` - -The predicate `may_read_path_for_canister` is defined as follows, implementing the access control outlined in [Request: Read state](#http-read-state): -``` -may_read_path_for_canister(S, _, ["time"]) = True -may_read_path_for_canister(S, _, ["subnet"]) = True -may_read_path_for_canister(S, _, ["subnet", sid]) = True -may_read_path_for_canister(S, _, ["subnet", sid, "public_key"]) = True -may_read_path_for_canister(S, _, ["subnet", sid, "type"]) = True -may_read_path_for_canister(S, _, ["subnet", sid, "canister_ranges"]) = sid == root_subnet_id -may_read_path_for_canister(S, _, ["subnet", sid, "node"]) = True -may_read_path_for_canister(S, _, ["subnet", sid, "node", nid]) = True -may_read_path_for_canister(S, _, ["subnet", sid, "node", nid, "public_key"]) = True -may_read_path_for_canister(S, _, ["request_status", Rid]) = -may_read_path_for_canister(S, _, ["request_status", Rid, "status"]) = -may_read_path_for_canister(S, _, ["request_status", Rid, "reply"]) = -may_read_path_for_canister(S, _, ["request_status", Rid, "reject_code"]) = -may_read_path_for_canister(S, _, ["request_status", Rid, "reject_message"]) = -may_read_path_for_canister(S, _, ["request_status", Rid, "error_code"]) = - ∀ (R ↦ (_, ECID')) ∈ dom(S.requests). hash_of_map(R) = Rid => RS.sender == R.sender ∧ ECID == ECID' -may_read_path_for_canister(S, _, ["canister", cid, "module_hash"]) = cid == ECID -may_read_path_for_canister(S, _, ["canister", cid, "controllers"]) = cid == ECID -may_read_path_for_canister(S, _, ["canister", cid, "metadata", name]) = cid == ECID ∧ UTF8(name) ∧ - (cid ∉ dom(S.canisters[cid]) ∨ - S.canisters[cid] = EmptyCanister ∨ - name ∉ (dom(S.canisters[cid].public_custom_sections) ∪ dom(S.canisters[cid].private_custom_sections)) ∨ - name ∈ dom(S.canisters[cid].public_custom_sections) ∨ - (name ∈ dom(S.canisters[cid].private_custom_sections) ∧ RS.sender ∈ S.controllers[cid]) - ) -may_read_path_for_canister(S, _, _) = False -``` - -where `UTF8(name)` holds if `name` is encoded in UTF-8. - -Submitted request to `/api/v3/subnet//read_state` - -```html - -E : Envelope - -``` - -Conditions - -```html - -E.content = ReadState RS -verify_envelope(E, RS.sender, S.system_time) -|E.content.nonce| <= 32 -S.system_time <= RS.ingress_expiry -∀ path ∈ RS.paths. may_read_path_for_subnet(S, RS.sender, path) - -``` - -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): -``` -may_read_path_for_subnet(S, _, ["time"]) = True -may_read_path_for_subnet(S, _, ["canister_ranges", sid]) = True -may_read_path_for_subnet(S, _, ["subnet"]) = True -may_read_path_for_subnet(S, _, ["subnet", sid]) = True -may_read_path_for_subnet(S, _, ["subnet", sid, "public_key"]) = True -may_read_path_for_subnet(S, _, ["subnet", sid, "type"]) = True -may_read_path_for_subnet(S, _, ["subnet", sid, "canister_ranges"]) = sid == root_subnet_id -may_read_path_for_subnet(S, _, ["subnet", sid, "metrics"]) = sid == -may_read_path_for_subnet(S, _, ["subnet", sid, "node"]) = True -may_read_path_for_subnet(S, _, ["subnet", sid, "node", nid]) = True -may_read_path_for_subnet(S, _, ["subnet", sid, "node", nid, "public_key"]) = True -may_read_path_for_subnet(S, _, _) = False -``` -The response is a certificate `cert`, as specified in [Certification](#certification), which passes `verify_cert` (assuming `S.root_key` as the root of trust), and where for every `path` documented in [The system state tree](#state-tree) that has a path in `RS.paths` or `["time"]` as a prefix, we have -``` -lookup_in_tree(path, cert.tree) = lookup_in_tree(path, state_tree(S)) -``` -where `state_tree` constructs a labeled tree from the IC state `S` and the (so far underspecified) set of subnets `subnets`, as per [The system state tree](#state-tree) -``` -state_tree(S) = { - "time": S.system_time; - "canister_ranges": { subnet_id : { canister_id : ranges | the lexicographically sorted list of ranges in subnet_ranges is split into chunks starting at canister_id } | (subnet_id, _, subnet_ranges, _) ∈ subnets }; - "subnet": { subnet_id : { "public_key" : subnet_pk; "type" : ; "metrics" : ; "node": { node_id : { "public_key" : node_pk } | (node_id, node_pk) ∈ subnet_nodes } } | (subnet_id, subnet_pk, subnet_ranges, subnet_nodes) ∈ subnets }; - "subnet": { subnet_id : { "canister_ranges" : subnet_ranges } | (subnet_id, _, subnet_ranges, _) ∈ subnets ∧ subnet_id == root_subnet_id }; - "request_status": { request_id(R): request_status_tree(T) | (R ↦ (T, _)) ∈ S.requests }; - "canister": - { canister_id : - { "module_hash" : SHA256(C.raw_module) | if C ≠ EmptyCanister } ∪ - { "controllers" : CBOR(S.controllers[canister_id]) } ∪ - { "metadata": { name: blob | (name, blob) ∈ S.canisters[canister_id].public_custom_sections ∪ S.canisters[canister_id].private_custom_sections } } - | (canister_id, C) ∈ S.canisters }; -} - -request_status_tree(Received) = - { "status": "received" } -request_status_tree(Processing) = - { "status": "processing" } -request_status_tree(Rejected (code, msg)) = - { "status": "rejected"; "reject_code": code; "reject_message": msg; "error_code": } -request_status_tree(Replied arg) = - { "status": "replied"; "reply": arg } -request_status_tree(Done) = - { "status": "done" } -``` - -and where `lookup_in_tree` is a function that returns `Found v` for a value `v`, `Absent`, or `Error`, appropriately. See the Section [Lookup](#lookup) for more details. - -### Abstract Canisters to System API {#concrete-canisters} - -In Section [Abstract canisters](#abstract-canisters) we introduced an abstraction over the interface to a canister, to avoid cluttering the abstract specification of the Internet Computer from WebAssembly details. In this section, we will fill the gap and explain how the abstract canister interface maps to the [concrete System API](#system-api) and the WebAssembly concepts as defined in the [WebAssembly specification](https://webassembly.github.io/spec/core/index.html). - -#### The concrete `Callback` - -The abstract `Callback` type above models an entry point for responses: -``` -I ∈ {i32, i64} -Closure = { - fun : I, - env : I, -} -Callback = { - on_reply : Closure; - on_reject : Closure; - on_cleanup : Closure | NoClosure; -} -``` - -#### The execution state - -We can model the execution of WebAssembly functions as stateful functions that have access to the WASM memory (a.k.a. heap) and (exported or mutable) globals in `WasmState`. In order to also model the behavior of the system imports, which have access to additional data structures, we extend the state as follows: -``` -Params = { - arg : NoArg | Blob; - caller : Principal; - caller_info_data : Blob; - caller_info_signer : Blob; - reject_code : 0 | SYS_FATAL | SYS_TRANSIENT | …; - reject_message : Text; - sysenv : Env; - cycles_refunded : Nat; - method_name : NoText | Text; - deadline : NoDeadline | Timestamp; -} -ExecutionState = { - wasm_state : WasmState; - params : Params; - response : NoResponse | Response; - cycles_accepted : Nat; - cycles_available : Nat; - cycles_used : Nat; - balance : Nat; - reply_params : { arg : Blob }; - pending_call : MethodCall | NoPendingCall; - calls : List MethodCall; - new_certified_data : NoCertifiedData | Blob; - new_global_timer : NoGlobalTimer | Nat; - ingress_filter : Accept | Reject; - context : I | G | U | Q | CQ | Ry | Rt | CRy | CRt | C | CC | F | T | s; -} -``` - -This allows us to model WebAssembly functions, including host-provided imports, as functions with implicit mutable access to an `ExecutionState`, dubbed *execution functions*. Syntactically, we express this using an implicit argument of type `ref ExecutionState` in angle brackets (e.g. `func(x)` for the invocation of a WebAssembly function with type `(x : i32) -> ()`). The lifetime of the `ExecutionState` data structure is that of one such function invocation. - -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_snapshots, - es.params.sysenv.subnet_size, - ) - ) -``` - -- For more convenience when creating a new `ExecutionState`, we define the following partial records: - ``` - empty_params = { - arg = NoArg; - caller = ic_principal; - caller_info_data = ""; - caller_info_signer = ""; - reject_code = 0; - reject_message = ""; - sysenv = (undefined); - cycles_refunded = 0; - method_name = NoText; - deadline = NoDeadline; - } - empty_execution_state = { - wasm_state = (undefined); - params = (undefined); - response = NoResponse; - cycles_accepted = 0; - cycles_available = 0; - cycles_used = 0; - balance = 0; - reply_params = { arg = "" }; - pending_call = NoPendingCall; - calls = []; - new_certified_data = NoCertifiedData; - new_global_timer = NoGlobalTimer; - ingress_filter = Reject; - context = (undefined); - } - ``` - -#### The concrete `CanisterModule` - -Finally, we can specify the abstract `CanisterModule` that models a concrete WebAssembly module. - -- We define the initial values `initial_globals` of the (exported or mutable) globals declared in the WebAssembly module. - -- We define a helper `table` which is an array of all functions of the WebAssembly module listed in its (unique according to Section [WebAssembly module requirements](#system-api-module)) table. - -- We define a helper function - ``` - start : (WasmState) -> Trap { cycles_used : Nat; } | Return { - new_state : WasmState; - cycles_used : Nat; - } - ``` - - modelling execution of a potential `(start)` function. - - If the WebAssembly module does not export a function called under the name `start`, then - ``` - start = λ (wasm_state) → - Return { - new_state = wasm_state; - cycles_used = 0; - } - ``` - - Otherwise, if the WebAssembly module exports a function `func` under the name `start`, it is - ``` - start = λ (wasm_state) → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - context = s; - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - Return { - new_state = es.wasm_state; - cycles_used = es.cycles_used; - } - ``` - - Note that `params` are undefined in the `(start)` function's execution state which is fine because the System API does not have access to that part of the execution state during the execution of the `(start)` function. - -- The `init` field of the `CanisterModule` is defined as follows: - - If the WebAssembly module does not export a function called under the name `canister_init`, then - ``` - init = λ (self_id, arg, caller, sysenv) → - match start({wasm_memory = ""; stable_memory = ""; globals = initial_globals; self_id = self_id;}) with - Trap trap → Trap trap - Return res → Return { - new_state = res.wasm_state; - new_certified_data = NoCertifiedData; - new_global_timer = NoGlobalTimer; - cycles_used = res.cycles_used; - } - ``` - - Otherwise, if the WebAssembly module exports a function `func` under the name `canister_init`, it is - ``` - init = λ (self_id, arg, caller, sysenv) → - match start({wasm_memory = ""; stable_memory = ""; globals = initial_globals; self_id = self_id;}) with - Trap trap → Trap trap - Return res → - let es = ref {empty_execution_state with - wasm_state = res.wasm_state - params = empty_params with { - arg = arg; - caller = caller; - sysenv = sysenv with { - balance = sysenv.balance - res.cycles_used - } - } - balance = sysenv.balance - res.cycles_used - context = I - } - try func() with Trap then Trap {cycles_used = res.cycles_used + es.cycles_used;} - Return { - new_state = es.wasm_state; - new_certified_data = es.new_certified_data; - new_global_timer = es.new_global_timer; - cycles_used = res.cycles_used + es.cycles_used; - } - ``` - -- The `pre_upgrade` field of the `CanisterModule` is defined as follows: - - If the WebAssembly module does not export a function called under the name `canister_pre_upgrade`, then it simply returns the current state: - ``` - pre_upgrade = λ (old_state, caller, sysenv) → Return {new_state = old_state; new_certified_data = NoCertifiedData; cycles_used = 0;} - ``` - - Otherwise, if the WebAssembly module exports a function `func` under the name `canister_pre_upgrade`, it is - ``` - pre_upgrade = λ (old_state, caller, sysenv) → - let es = ref {empty_execution_state with - wasm_state = old_state - params = empty_params with { caller = caller; sysenv } - balance = sysenv.balance - context = G - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - Return { - new_state = es.wasm_state; - new_certified_data = es.new_certified_data; - cycles_used = es.cycles_used; - } - ``` - -- The `post_upgrade` field of the `CanisterModule` is defined as follows: - - If the WebAssembly module does not export a function called under the name `canister_post_upgrade`, then - ``` - post_upgrade = λ (wasm_state, arg, caller, sysenv) → - match start(wasm_state) with - Trap trap → Trap trap - Return res → Return { - new_state = res.wasm_state; - new_certified_data = NoCertifiedData; - new_global_timer = NoGlobalTimer; - cycles_used = res.cycles_used; - } - ``` - - Otherwise, if the WebAssembly module exports a function `func` under the name `canister_post_upgrade`, it is - ``` - post_upgrade = λ (wasm_state, arg, caller, sysenv) → - match start(wasm_state) with - Trap trap → Trap trap - Return res → - let es = ref {empty_execution_state with - wasm_state = res.wasm_state - params = empty_params with { - arg = arg; - caller = caller; - sysenv = sysenv with { - balance = sysenv.balance - res.cycles_used - } - } - balance = sysenv.balance - res.cycles_used - context = I - } - try func() with Trap then Trap {cycles_used = res.cycles_used + es.cycles_used;} - Return { - new_state = es.wasm_state; - new_certified_data = es.new_certified_data; - new_global_timer = es.new_global_timer; - cycles_used = res.cycles_used + es.cycles_used; - } - ``` - -- The partial map `update_methods` of the `CanisterModule` is defined for all method names `method` for which the WebAssembly program exports a function `func` named `canister_update `, and has value - ``` - update_methods[method] = λ (arg, caller, caller_info_data, caller_info_signer, deadline, sysenv, available) → λ wasm_state → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = empty_params with { - arg = arg; - caller = caller; - caller_info_data = caller_info_data; - caller_info_signer = caller_info_signer; - deadline = deadline; - sysenv; - } - balance = sysenv.balance - cycles_available = available; - context = U - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - discard_pending_call() - Return { - new_state = es.wasm_state; - new_calls = es.calls; - new_certified_data = es.new_certified_data; - new_global_timer = es.new_global_timer; - response = es.response; - cycles_accepted = es.cycles_accepted; - cycles_used = es.cycles_used; - } - ``` - -- The partial map `query_methods` of the `CanisterModule` is defined for all method names `method` for which the WebAssembly program exports a function `func` named `canister_query `, and has value - ``` - query_methods[method] = λ (arg, caller, caller_info_data, caller_info_signer, sysenv, available) → λ wasm_state → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = empty_params with { - arg = arg; - caller = caller; - caller_info_data = caller_info_data; - caller_info_signer = caller_info_signer; - sysenv - } - balance = sysenv.balance - cycles_available = available - context = Q - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - Return { - response = es.response; - cycles_accepted = es.cycles_accepted; - cycles_used = es.cycles_used; - } - ``` - - By construction, the (possibly modified) `es.wasm_state` is discarded. - -- The partial map `composite_query_methods` of the `CanisterModule` is defined for all method names `method` for which the WebAssembly program exports a function `func` named `canister_composite_query `, and has value - ``` - composite_query_methods[method] = λ (arg, caller, caller_info_data, caller_info_signer, sysenv) → λ wasm_state → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = empty_params with { - arg = arg; - caller = caller; - caller_info_data = caller_info_data; - caller_info_signer = caller_info_signer; - sysenv - } - balance = sysenv.balance - context = CQ - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - discard_pending_call() - Return { - new_state = es.wasm_state; - new_calls = es.calls; - response = es.response; - cycles_used = es.cycles_used; - } - ``` - -- The function `heartbeat` of the `CanisterModule` is defined if the WebAssembly program exports a function `func` named `canister_heartbeat`, and has value - ``` - heartbeat = λ (sysenv) → λ wasm_state → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = empty_params with { arg = NoArg; caller = ic_principal; sysenv } - balance = sysenv.balance - context = T - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - discard_pending_call() - Return { - new_state = es.wasm_state; - new_calls = es.calls; - new_certified_data = es.certified_data; - new_global_timer = es.new_global_timer; - cycles_used = es.cycles_used; - } - ``` - - otherwise it is - -```html - -heartbeat = λ (sysenv) → λ wasm_state → Trap {cycles_used = 0;} - -``` - -- The function `global_timer` of the `CanisterModule` is defined if the WebAssembly program exports a function `func` named `canister_global_timer`, and has value - ``` - global_timer = λ (sysenv) → λ wasm_state → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = empty_params with { arg = NoArg; caller = ic_principal; sysenv } - balance = sysenv.balance - context = T - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - discard_pending_call() - Return { - new_state = es.wasm_state; - new_calls = es.calls; - new_certified_data = es.certified_data; - new_global_timer = es.new_global_timer; - cycles_used = es.cycles_used; - } - ``` - - otherwise it is - -```html - -global_timer = λ (sysenv) → λ wasm_state → Trap {cycles_used = 0;} - -``` - -- The function `on_low_wasm_memory` of the `CanisterModule` is defined if the WebAssembly program exports a function `func` named `canister_on_low_wasm_memory`, and has value - ``` - on_low_wasm_memory = λ (sysenv) → λ wasm_state → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = empty_params with { arg = NoArg; caller = ic_principal; sysenv } - balance = sysenv.balance - context = T - } - try func() with Trap then Trap {cycles_used = es.cycles_used;} - discard_pending_call() - Return { - new_state = es.wasm_state; - new_calls = es.calls; - new_certified_data = es.certified_data; - new_global_timer = es.new_global_timer; - cycles_used = es.cycles_used; - } - ``` - - otherwise it is - - ```html - on_low_wasm_memory = λ (sysenv) → λ wasm_state → Trap {cycles_used = 0;} - ``` - -- The function `callbacks` of the `CanisterModule` is defined as follows - ``` - I ∈ {i32, i64} - callbacks = λ(callbacks, caller, caller_info_data, caller_info_signer, response, deadline, refunded_cycles, sysenv, available) → λ wasm_state → - let params0 = empty_params with { - caller = caller; - caller_info_data = caller_info_data; - caller_info_signer = caller_info_signer; - sysenv; - cycles_refunded = refund_cycles; - deadline; - } - let (fun, env, params, context) = match response with - Reply data -> - (callbacks.on_reply.fun, callbacks.on_reply.env, - { params0 with data}, Ry) - Reject (reject_code, reject_message)-> - (callbacks.on_reject.fun, callbacks.on_reject.env, - { params0 with reject_code; reject_message}, Rt) - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = params; - balance = sysenv.balance; - cycles_available = available; - context = context; - } - try - if fun > |table| then Trap - let func = table[fun] - if typeof(func) ≠ func (I) -> () then Trap - func(env) - discard_pending_call() - Return { - new_state = es.wasm_state; - new_calls = es.calls; - new_certified_data = es.certified_data; - new_global_timer = es.new_global_timer; - response = es.response; - cycles_accepted = es.cycles_accepted; - cycles_used = es.cycles_used; - } - with Trap - if callbacks.on_cleanup = NoClosure then Trap {cycles_used = es.cycles_used;} - if callbacks.on_cleanup.fun > |table| then Trap {cycles_used = es.cycles_used;} - let func = table[callbacks.on_cleanup.fun] - if typeof(func) ≠ func (I) -> () then Trap {cycles_used = es.cycles_used;} - - let es' = ref { empty_execution_state with - wasm_state = wasm_state; - params = params; - balance = sysenv.balance - es.cycles_used; - context = C; - } - try func(callbacks.on_cleanup.env) with Trap then Trap {cycles_used = es.cycles_used + es'.cycles_used;} - Return { - new_state = es'.wasm_state; - new_calls = []; - new_certified_data = NoCertifiedData; - new_global_timer = es'.new_global_timer; - response = NoResponse; - cycles_accepted = 0; - cycles_used = es.cycles_used + es'.cycles_used; - } - ``` - - Note that if the initial callback handler traps, the cleanup callback (if present) is executed, and the canister has the chance to update its state. - -- The function `composite_callbacks` of the `CanisterModule` is defined as follows - ``` - I ∈ {i32, i64} - composite_callbacks = λ(callbacks, caller, caller_info_data, caller_info_signer, response, sysenv) → λ wasm_state → - let params0 = empty_params with { - caller = caller; - caller_info_data = caller_info_data; - caller_info_signer = caller_info_signer; - sysenv - } - let (fun, env, params, context) = match response with - Reply data -> - (callbacks.on_reply.fun, callbacks.on_reply.env, - { params0 with data}, CRy) - Reject (reject_code, reject_message)-> - (callbacks.on_reject.fun, callbacks.on_reject.env, - { params0 with reject_code; reject_message}, CRt) - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = params; - balance = sysenv.balance; - context = context; - } - try - if fun > |table| then Trap - let func = table[fun] - if typeof(func) ≠ func (I) -> () then Trap - func(env) - discard_pending_call() - Return { - new_state = es.wasm_state; - new_calls = es.calls; - response = es.response; - cycles_used = es.cycles_used; - } - with Trap - if callbacks.on_cleanup = NoClosure then Trap {cycles_used = es.cycles_used;} - if callbacks.on_cleanup.fun > |table| then Trap {cycles_used = es.cycles_used;} - let func = table[callbacks.on_cleanup.fun] - if typeof(func) ≠ func (I) -> () then Trap {cycles_used = es.cycles_used;} - - let es' = ref { empty_execution_state with - wasm_state = wasm_state; - params = params; - balance = sysenv.balance - es.cycles_used; - context = CC; - } - try func(callbacks.on_cleanup.env) with Trap then Trap {cycles_used = es.cycles_used + es'.cycles_used;} - Return { - new_state = es'.wasm_state; - new_calls = []; - response = NoResponse; - cycles_used = es.cycles_used + es'.cycles_used; - } - ``` - - Note that if the initial callback handler traps, the cleanup callback (if present) is executed. - -- The `inspect_message` field of the `CanisterModule` is defined as follows. - - If the WebAssembly module does not export a function called under the name `canister_inspect_message`, then access is always granted: - ``` - inspect_message = λ (method_name, wasm_state, arg, caller, caller_info_data, caller_info_signer, sysenv) → - Return {status = Accept;} - ``` - - Otherwise, if the WebAssembly module exports a function `func` under the name `canister_inspect_message`, it is - ``` - inspect_message = λ (method_name, wasm_state, arg, caller, caller_info_data, caller_info_signer, sysenv) → - let es = ref {empty_execution_state with - wasm_state = wasm_state; - params = empty_params with { - arg = arg; - caller = caller; - caller_info_data = caller_info_data; - caller_info_signer = caller_info_signer; - method_name = method_name; - sysenv - } - balance = sysenv.balance; - cycles_available = 0; // ingress requests have no funds - context = F; - } - try func() with Trap then Trap - Return {status = es.ingress_filter;}; - ``` - -#### Helper functions - -In the following section, we use the these helper functions -``` -I ∈ {i32, i64} -copy_to_canister(dst : I, offset : I, size : I, data : blob) = - if offset+size > |data| then Trap {cycles_used = es.cycles_used;} - if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} - es.wasm_state.wasm_memory[dst..dst+size] := data[offset..offset+size] - -I ∈ {i32, i64} -copy_from_canister(src : I, size : I) blob = - if src+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} - return es.wasm_state.wasm_memory[src..src+size] -``` - -Cycles are represented by 128-bit values so they require 16 bytes of memory. -``` -I ∈ {i32, i64} -copy_cycles_to_canister(dst : I, data : blob) = - let size = 16; - if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} - es.wasm_state.wasm_memory[dst..dst+size] := data[0..size] -``` - -Helper function to get sorted keys from environment variables map. -``` -get_sorted_env_keys(env_vars : (text -> text)) = - let keys = [] - for (key, _) in env_vars: - keys := keys · [key] - return sort_lexicographically(keys) -``` - -#### System imports - -Upon *instantiation* of the WebAssembly module, we can provide the following functions as imports. - -The pseudo-code below does *not* explicitly enforce the restrictions of which imports are available in which contexts; for that the table in [Overview of imports](#system-api-imports) is authoritative, and is assumed to be part of the implementation. -``` -I ∈ {i32, i64} -ic0.msg_arg_data_size() : I = - if es.context ∉ {I, U, RQ, NRQ, TQ, CQ, Ry, CRy, F} then Trap {cycles_used = es.cycles_used;} - return |es.params.arg| - -I ∈ {i32, i64} -ic0.msg_arg_data_copy(dst : I, offset : I, size : I) = - if es.context ∉ {I, U, RQ, NRQ, TQ, CQ, Ry, CRy, F} then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.arg) - -I ∈ {i32, i64} -ic0.msg_caller_size() : I = - if es.context = s then Trap {cycles_used = es.cycles_used;} - return |es.params.caller| - -I ∈ {i32, i64} -ic0.msg_caller_copy(dst : I, offset : I, size : I) = - if es.context = s then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.caller) - -I ∈ {i32, i64} -ic0.msg_caller_info_data_size() : I = - if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} - return |es.params.caller_info_data| - -I ∈ {i32, i64} -ic0.msg_caller_info_data_copy(dst : I, offset : I, size : I) = - if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.caller_info_data) - -I ∈ {i32, i64} -ic0.msg_caller_info_signer_size() : I = - if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} - return |es.params.caller_info_signer| - -I ∈ {i32, i64} -ic0.msg_caller_info_signer_copy(dst : I, offset : I, size : I) = - if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.caller_info_signer) - -ic0.msg_reject_code() : i32 = - if es.context ∉ {Ry, Rt, CRy, CRt, C} then Trap {cycles_used = es.cycles_used;} - es.params.reject_code - -I ∈ {i32, i64} -ic0.msg_reject_msg_size() : I = - if es.context ∉ {Rt, CRt} then Trap {cycles_used = es.cycles_used;} - return |es.params.reject_msg| - -I ∈ {i32, i64} -ic0.msg_reject_msg_copy(dst : I, offset : I, size : I) = - if es.context ∉ {Rt, CRt} then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.reject_msg) - -ic0.msg_deadline() : i64 = - if es.context ∉ {U, Q, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} - if es.params.deadline = Timestamp t - then return t - else return 0 - -I ∈ {i32, i64} -ic0.msg_reply_data_append(src : I, size : I) = - if es.context ∉ {U, RQ, NRQ, TQ, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} - if es.response ≠ NoResponse then Trap {cycles_used = es.cycles_used;} - es.reply_params.arg := es.reply_params.arg · copy_from_canister(src, size) - -ic0.msg_reply() = - if es.context ∉ {U, RQ, NRQ, TQ, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} - if es.response ≠ NoResponse then Trap {cycles_used = es.cycles_used;} - es.response := Reply (es.reply_params.arg) - es.cycles_available := 0 - -I ∈ {i32, i64} -ic0.msg_reject(src : I, size : I) = - if es.context ∉ {U, RQ, NRQ, TQ, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} - if es.response ≠ NoResponse then Trap {cycles_used = es.cycles_used;} - es.response := Reject (CANISTER_REJECT, copy_from_canister(src, size)) - es.cycles_available := 0 - -ic0.msg_cycles_available() : i64 = - if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} - if es.cycles_available >= 2^64 then Trap {cycles_used = es.cycles_used;} - return es.cycles_available - -I ∈ {i32, i64} -ic0.msg_cycles_available128(dst : I) = - if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} - let amount = es.cycles_available - copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) - -ic0.msg_cycles_refunded() : i64 = - if es.context ∉ {Rt, Ry} then Trap {cycles_used = es.cycles_used;} - if es.params.cycles_refunded >= 2^64 then Trap {cycles_used = es.cycles_used;} - return es.params.cycles_refunded - -I ∈ {i32, i64} -ic0.msg_cycles_refunded128(dst : I) = - if es.context ∉ {Rt, Ry} then Trap {cycles_used = es.cycles_used;} - let amount = es.params.cycles_refunded - copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) - -ic0.msg_cycles_accept(max_amount : i64) : i64 = - if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} - let amount = min(max_amount, es.cycles_available) - es.cycles_available := es.cycles_available - amount - es.cycles_accepted := es.cycles_accepted + amount - es.balance := es.balance + amount - return amount - -I ∈ {i32, i64} -ic0.msg_cycles_accept128(max_amount_high : i64, max_amount_low : i64, dst : I) = - if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} - let max_amount = max_amount_high * 2^64 + max_amount_low - let amount = min(max_amount, es.cycles_available) - es.cycles_available := es.cycles_available - amount - es.cycles_accepted := es.cycles_accepted + amount - es.balance := es.balance + amount - copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) - -I ∈ {i32, i64} -ic0.cycles_burn128(amount_high : i64, amount_low : i64, dst : I) = - if es.context ∉ {I, G, U, RQ, 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)) - es.balance := es.balance - burned_amount - copy_cycles_to_canister(dst, burned_amount.to_little_endian_bytes()) - -I ∈ {i32, i64} -ic0.canister_self_size() : I = - if es.context = s then Trap {cycles_used = es.cycles_used;} - return |es.wasm_state.self_id| - -I ∈ {i32, i64} -ic0.canister_self_copy(dst : I, offset : I, size : I) = - if es.context = s then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.wasm_state.self_id) - -I ∈ {i32, i64} -ic0.subnet_self_size() : I = - if es.context = s then Trap {cycles_used = es.cycles_used;} - return |es.params.sysenv.subnet_id| - -I ∈ {i32, i64} -ic0.subnet_self_copy(dst : I, offset : I, size : I) = - if es.context = s then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.sysenv.subnet_id) - -ic0.canister_cycle_balance() : i64 = - if es.context = s then Trap {cycles_used = es.cycles_used;} - if es.balance >= 2^64 then Trap {cycles_used = es.cycles_used;} - return es.balance - -I ∈ {i32, i64} -ic0.canister_cycle_balance128(dst : I) = - if es.context = s then Trap {cycles_used = es.cycles_used;} - let amount = es.balance - copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) - -I ∈ {i32, i64} -ic0.canister_liquid_cycle_balance128(dst : I) = - if es.context = s then Trap {cycles_used = es.cycles_used;} - copy_cycles_to_canister(dst, liquid_balance(es).to_little_endian_bytes()) - -ic0.canister_status() : i32 = - if es.context = s then Trap {cycles_used = es.cycles_used;} - match es.params.sysenv.canister_status with - Running -> return 1 - Stopping -> return 2 - Stopped -> return 3 - -ic0.canister_version() : i64 = - if es.context = s then Trap {cycles_used = es.cycles_used;} - return es.params.sysenv.canister_version - -I ∈ {i32, i64} -ic0.msg_method_name_size() : I = - if es.context ∉ {F} then Trap {cycles_used = es.cycles_used;} - return |es.method_name| - -I ∈ {i32, i64} -ic0.msg_method_name_copy(dst : I, offset : I, size : I) = - if es.context ∉ {F} then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.method_name) - -ic0.accept_message() = - if es.context ∉ {F} then Trap {cycles_used = es.cycles_used;} - if es.ingress_filter = Accept then Trap {cycles_used = es.cycles_used;} - es.ingress_filter = Accept - -I ∈ {i32, i64} -ic0.call_new( - callee_src : I, - callee_size : I, - name_src : I, - name_size : I, - reply_fun : I, - reply_env : I, - reject_fun : I, - reject_env : I, - ) = - if es.context ∉ {U, CQ, Ry, Rt, CRy, CRt, T} then Trap {cycles_used = es.cycles_used;} - - discard_pending_call() - - callee := copy_from_canister(callee_src, callee_size); - method_name := copy_from_canister(name_src, name_size); - - es.pending_call = MethodCall { - callee = callee; - method_name = callee; - arg = ""; - transferred_cycles = 0; - callback = Callback { - on_reply = Closure { fun = reply_fun; env = reply_env } - on_reject = Closure { fun = reject_fun; env = reject_env } - on_cleanup = NoClosure - }; - } - -ic0.call_with_best_effort_response(timeout_seconds : i32) = - if - es.context ∉ {U, CQ, Ry, Rt, CRy, CRt, T} - or es.pending_call = NoPendingCall - or es.pending_call.timeout_seconds ≠ NoTimeout - then Trap {cycles_used = es.cycles_used;} - es.pending_call.timeout_seconds := min(timeout_seconds, MAX_CALL_TIMEOUT) - -I ∈ {i32, i64} -ic0.call_on_cleanup (fun : I, env : I) = - 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;} - if es.pending_call.callback.on_cleanup ≠ NoClosure then Trap {cycles_used = es.cycles_used;} - es.pending_call.callback.on_cleanup := Closure { fun = fun; env = env} - -I ∈ {i32, i64} -ic0.call_data_append (src : I, size : I) = - 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.pending_call.arg := es.pending_call.arg · copy_from_canister(src, size) - -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) < 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) < 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_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;} - - // `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 - -// helper function -discard_pending_call() = - if es.pending_call ≠ NoPendingCall then - es.balance := es.balance + es.pending_call.transferred_cycles - es.pending_call := NoPendingCall - -ic0.stable_size() : (page_count : i32) = - if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} - page_count := |es.wasm_state.stable_memory| / 64k - return page_count - -ic0.stable_grow(new_pages : i32) : (old_page_count : i32) = - if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} - if arbitrary() then return -1 - else - old_size := |es.wasm_state.stable_memory| / 64k - if old_size + new_pages > 2^16 then return -1 - es.wasm_state.stable_memory := - es.wasm_state.stable_memory · repeat(0x00, new_pages * 64k) - return old_size - -ic0.stable_write(offset : i32, src : i32, size : i32) - if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} - if src+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} - if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} - - es.wasm_state.stable_memory[offset..offset+size] := es.wasm_state.wasm_memory[src..src+size] - -ic0.stable_read(dst : i32, offset : i32, size : i32) - if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} - if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} - if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} - - es.wasm_state.wasm_memory[offset..offset+size] := es.wasm_state.stable_memory[src..src+size] - -ic0.stable64_size() : (page_count : i64) = - return |es.wasm_state.stable_memory| / 64k - -ic0.stable64_grow(new_pages : i64) : (old_page_count : i64) = - if arbitrary() - then return -1 - else - old_size := |es.wasm_state.stable_memory| / 64k - es.wasm_state.stable_memory := - es.wasm_state.stable_memory · repeat(0x00, new_pages * 64k) - return old_size - -ic0.stable64_write(offset : i64, src : i64, size : i64) - if src+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} - if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} - - es.wasm_state.stable_memory[offset..offset+size] := es.wasm_state.wasm_memory[src..src+size] - -ic0.stable64_read(dst : i64, offset : i64, size : i64) - if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} - if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} - - es.wasm_state.wasm_memory[offset..offset+size] := es.wasm_state.stable_memory[src..src+size] - -I ∈ {i32, i64} -ic0.root_key_size() : I = - if es.context ∉ {I, G, U, RQ, Ry, Rt, C, T} then Trap {cycles_used = es.cycles_used;} - let root_key = - return |root_key| - -I ∈ {i32, i64} -ic0.root_key_copy(dst : I, offset : I, size : I) = - if es.context ∉ {I, G, U, RQ, Ry, Rt, C, T} then Trap {cycles_used = es.cycles_used;} - let root_key = - copy_to_canister(dst, offset, size, root_key) - -I ∈ {i32, i64} -ic0.certified_data_set(src : I, size : I) = - if es.context ∉ {I, G, U, Ry, Rt, T} then Trap {cycles_used = es.cycles_used;} - es.new_certified_data := es.wasm_state[src..src+size] - -ic0.data_certificate_present() : i32 = - if es.context = s then Trap {cycles_used = es.cycles_used;} - if es.params.sysenv.certificate = NoCertificate - then return 0 - else return 1 - -I ∈ {i32, i64} -ic0.data_certificate_size() : I = - if es.context ∉ {NRQ, CQ} then Trap {cycles_used = es.cycles_used;} - if es.params.sysenv.certificate = NoCertificate then Trap {cycles_used = es.cycles_used;} - return |es.params.sysenv.certificate| - -I ∈ {i32, i64} -ic0.data_certificate_copy(dst : I, offset : I, size : I) = - if es.context ∉ {NRQ, CQ} then Trap {cycles_used = es.cycles_used;} - if es.params.sysenv.certificate = NoCertificate then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, es.params.sysenv.certificate) - -ic0.time() : i64 = - if es.context = s then Trap {cycles_used = es.cycles_used;} - return es.params.sysenv.time - -ic0.global_timer_set(timestamp: i64) : i64 = - if es.context ∉ {I, G, U, Ry, Rt, C, T} then Trap {cycles_used = es.cycles_used;} - let prev_global_timer = es.new_global_timer - es.new_global_timer := timestamp - if prev_global_timer = NoGlobalTimer - then return es.params.sysenv.global_timer - else return prev_global_timer - -ic0.performance_counter(counter_type : i32) : i64 = - arbitrary() - -I ∈ {i32, i64} -ic0.is_controller(src : I, size : I) : (result: i32) = - bytes = copy_from_canister(src, size) - if bytes encode a principal then - if bytes ∉ es.params.sysenv.controllers - then return 0 - else return 1 - else - Trap {cycles_used = es.cycles_used;} - -ic0.in_replicated_execution() : i32 = - if es.context ∈ {I, G, U, RQ, Ry, Rt, C, T, s} - then return 1 - else return 0 - -I ∈ {i32, i64} -ic0.cost_call(method_name_size: i64, payload_size: i64, dst: I) : () = - copy_cycles_to_canister(dst, arbitrary()) - -I ∈ {i32, i64} -ic0.cost_create_canister(dst: I) : () = - copy_cycles_to_canister(dst, arbitrary()) - -I ∈ {i32, i64} -ic0.cost_http_request(request_size: i64, max_res_bytes: i64, dst: I) : () = - copy_cycles_to_canister(dst, arbitrary()) - -I ∈ {i32, i64} -ic0.cost_sign_with_ecdsa(src: I, size: I, ecdsa_curve: i32, dst: I) : i32 = - known_keys = arbitrary() - known_curves = arbitrary() - key_name = copy_from_canister(src, size) - if ecdsa_curve ∉ known_curves then - return 1 - if key_name ∉ known_keys then - return 2 - copy_cycles_to_canister(dst, arbitrary()) - return 0 - -I ∈ {i32, i64} -ic0.cost_sign_with_schnorr(src: I, size: I, algorithm: i32, dst: I) : i32 = - known_keys = arbitrary() - known_algorithms = arbitrary() - key_name = copy_from_canister(src, size) - if algorithm ∉ known_algorithms then - return 1 - if key_name ∉ known_keys then - return 2 - copy_cycles_to_canister(dst, arbitrary()) - return 0 - -I ∈ {i32, i64} -ic0.cost_vetkd_derive_key(src: I, size: I, vetkd_curve: i32, dst: I) : i32 = - known_keys = arbitrary() - known_curves = arbitrary() - key_name = copy_from_canister(src, size) - if vetkd_curve ∉ known_curves then - return 1 - if key_name ∉ known_keys then - return 2 - copy_cycles_to_canister(dst, arbitrary()) - return 0 - -I ∈ {i32, i64} -ic0.env_var_count() : I = - if es.context = s then Trap {cycles_used = es.cycles_used;} - return |es.params.sysenv.environment_variables| - -I ∈ {i32, i64} -ic0.env_var_name_size(index : I) : I = - if es.context = s then Trap {cycles_used = es.cycles_used;} - if index >= |es.params.sysenv.environment_variables| then Trap {cycles_used = es.cycles_used;} - let sorted_keys = get_sorted_env_keys(es.params.sysenv.environment_variables) - return |sorted_keys[index]| - -I ∈ {i32, i64} -ic0.env_var_name_copy(index : I, dst : I, offset : I, size : I) = - if es.context = s then Trap {cycles_used = es.cycles_used;} - if index >= |es.params.sysenv.environment_variables| then Trap {cycles_used = es.cycles_used;} - let sorted_keys = get_sorted_env_keys(es.params.sysenv.environment_variables) - let name_var = sorted_keys[index] - copy_to_canister(dst, offset, size, name_var) - -I ∈ {i32, i64} -ic0.env_var_name_exists(name_src : I, name_size : I) : i32 = - if es.context = s then Trap {cycles_used = es.cycles_used;} - if name_size > MAX_ENV_VAR_NAME_LENGTH then Trap {cycles_used = es.cycles_used;} - let name_var = copy_from_canister(name_src, name_size) - if !is_valid_utf8(name_var) then Trap {cycles_used = es.cycles_used;} - if value_var ∈ dom(es.params.sysenv.environment_variables) then - return 1 - else - return 0 - -I ∈ {i32, i64} -ic0.env_var_value_size(name_src : I, name_size : I) : I = - if es.context = s then Trap {cycles_used = es.cycles_used;} - if name_size > MAX_ENV_VAR_NAME_LENGTH then Trap {cycles_used = es.cycles_used;} - let name_var = copy_from_canister(name_src, name_size) - if !is_valid_utf8(name_var) then Trap {cycles_used = es.cycles_used;} - let value_var = es.params.sysenv.environment_variables[name_var] - if value_var = null then Trap {cycles_used = es.cycles_used;} - return |value_var| - -I ∈ {i32, i64} -ic0.env_var_value_copy(name_src : I, name_size : I, dst : I, offset : I, size : I) = - if es.context = s then Trap {cycles_used = es.cycles_used;} - if name_size > MAX_ENV_VAR_NAME_LENGTH then Trap {cycles_used = es.cycles_used;} - let name_var = copy_from_canister(name_src, name_size) - if !is_valid_utf8(name_var) then Trap {cycles_used = es.cycles_used;} - let value_var = es.params.sysenv.environment_variables[name_var] - if value_var = null then Trap {cycles_used = es.cycles_used;} - copy_to_canister(dst, offset, size, value_var) - -I ∈ {i32, i64} -ic0.debug_print(src : I, size : I) = - return - -I ∈ {i32, i64} -ic0.trap(src : I, size : I) = - Trap {cycles_used = es.cycles_used;} -``` - -## Changelog {#changelog} - -### 0.58.0 (2025-04-01) {$0_58_0} -* Added subnet admins who can start, stop, uninstall, delete a canister and request its status. - -### 0.57.0 (2025-03-30) {$0_57_0} -* Added canister snapshot visibility settings. - -### 0.56.0 (2025-03-16) {$0_56_0} -* Support for the HTTP methods `PUT` and `DELETE` in canister `http_request` in non-replicated mode. -* Added subnet type to the certified state tree at the path `/subnet//type`. - -### 0.55.0 (2025-01-26) {$0_55_0} -* Support for canister renaming (required for canister ID migration from one subnet to another subnet). - -### 0.54.0 (2025-12-15) {$0_54_0} -* The management canister's endpoint `take_canister_snapshot` can uninstall code atomically after taking canister snapshot. -* The canister system API `ic0.msg_reject_code` is available in cleanup callbacks. - -### 0.53.0 (2025-11-24) {$0_53_0} -* New execution context `TQ` for canister http transform functions to specify that no IC certificate is available in such executions. - -### 0.52.0 (2025-11-17) {$0_52_0} -* Canister memory allocation does not limit canister memory usage. - -### 0.51.0 (2025-10-20) {$0_51_0} -* Management canister endpoint `canister_status` can be invoked as a query call. - -### 0.50.0 (2025-10-13) {$0_50_0} -* Allow loading a canister snapshot onto a different canister on the same subnet. -* New management canister API providing canister metadata (contained in canister WASM custom sections). -* New HTTP handler endpoints to support canister migration (by providing sharded routing table in certificates): - `/api/v4/canister//call`, - `/api/v3/canister//read_state`, - `/api/v3/subnet//read_state`, and - `/api/v3/canister//query`. -* The following existing HTTP handler endpoints are marked as deprecated: - `/api/v3/canister//call`, - `/api/v2/canister//read_state`, - `/api/v2/subnet//read_state`, and - `/api/v2/canister//query`. -* Paths with prefix `/subnet//canister_ranges` (legacy routing table) can only be requested via the deprecated endpoints or if `` is the root subnet. -* Paths with prefix `/canister_ranges/` (sharded routing table) can only be requested via `/api/v{2,3}/subnet/.../read_state`, i.e., - not via `/api/v{2,3}/canister/.../read_state`. -* All paths with prefix `/canister_ranges/` must refer to the same subnet ID ``. - -### 0.49.0 (2025-10-06) {$0_49_0} -* Added support for non-replicated canister HTTP outcalls. - -### 0.48.0 (2025-09-29) {$0_48_0} -* Added support for canister environment variables in canister settings and new System API for accessing environment variables - (`ic0.env_var_count`, `ic0.env_var_name_size`, `ic0.env_var_name_copy`, and `ic0.env_var_value_size`). - -### 0.47.0 (2025-09-22) {$0_47_0} -* Management canister API for downloading and uploading canister snapshots. - -### 0.46.0 (2025-08-25) {$0_46_0} -* The management canister method `canister_status` returns two new fields: `version` indicating the canister version, and `ready_for_migration` indicating whether a canister's queues are empty and its streams flushed. The value only makes sense when the canister status is `stopped`. -* Canister history provides the source of a snapshot in the entry for loading the snapshot. - -### 0.45.0 (2025-08-18) {$0_45_0} -* Canister ranges of every subnet are now available at a dedicated prefix `/canister_ranges` in the state tree, - facilitating fragmentation due to canister migration. - -### 0.44.0 (2025-08-11) {$0_44_0} -* The management canister method `subnet_info` returns a new field `registry_version` providing the registry version of the corresponding subnet. - -### 0.43.0 (2025-07-17) {$0_43_0} -* VetKD API is considered stable. - -### 0.42.0 (2025-06-06) {#0_42_0} -* New system API `ic0.root_key_{size, copy}` for fetching the public key of the IC root key. - -### 0.41.0 (2025-06-02) {#0_41_0} -* Management canister API for threshold key derivation (vetKD). - -### 0.40.0 (2025-05-30) {#0_40_0} -* Non-ASCII characters are allowed in the URL of canister http outcalls. -* The transformed response size of canister http outcalls must not exceeded `max_response_bytes` (if provided). - -### 0.39.0 (2025-05-07) {#0_39_0} -* Threshold Schnorr API, composite query methods, and canister logs management canister API are considered stable. - -### 0.38.0 (2025-04-18) {#0_38_0} -* Reverted a lower bound of one week on the canister's freezing threshold. - -### 0.37.0 (2025-04-11) {#0_37_0} -* Introduced a lower bound of one week on the canister's freezing threshold. - -### 0.36.0 (2025-03-31) {#0_36_0} -* Bounded-wait calls. - -### 0.35.0 (2025-03-20) {#0_35_0} -* New system API `ic0.canister_liquid_cycle_balance128` returning the current amount of cycles that is available for spending in calls and execution. -* A canister can have multiple snapshots. - -### 0.34.0 (2025-03-07) {#0_34_0} -* New canister method `canister_on_low_wasm_memory` invoked when the canister is low on main memory according to a new `wasm_memory_threshold` in canister settings. -* New system APIs `ic0.cost_call`, `ic0.cost_create_canister`, `ic0.cost_http_request`, `ic0.cost_sign_with_ecdsa`, `ic0.cost_sign_with_schnorr`, and `ic0.cost_vetkd_derive_encrypted_key` for cycles cost calculation. -* New field `memory_metrics` providing detailed metrics on the memory consumption of a canister in the response of the management canister's `canister_status` endpoint. - -### 0.33.0 (2025-02-12) {#0_33_0} -* New system API `ic0.subnet_self_size` and `ic0.subnet_self_copy`. - -### 0.32.0 (2025-01-23) {#0_32_0} -* Allow accepting and burning cycles in replicated queries. - -### 0.31.0 (2025-01-09) {#0_31_0} -* Add support for Schnorr auxiliary inputs - -### 0.30.0 (2024-11-19) {#0_30_0} -* Add management canister endpoint `subnet_info`. -* Support for wasm64: 64-bit system API. - -### 0.29.0 (2024-11-14) {#0_29_0} -* Allow anonymous query and read state requests with invalid `ingress_expiry`. -* Add allowed viewers variant to canister log visibility. -* Deprecate the Bitcoin API of the management canister. - -### 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. - -### 0.25.0 (2024-06-14) {#0_25_0} -* Query call statistics. -* New `wasm_memory_persistence` option for canister upgrades. -* Rename `num_blocks_total` to `num_blocks_proposed_total` in node metrics served by the management canister. -* Management canister query call to fetch canister logs. -* WASM heap memory limit in canisters settings. -* 32-bit stable memory System API is marked DEPRECATED. -* Remove the management canister query calls `bitcoin_get_balance_query` and `bitcoin_get_utxos_query`. - -### 0.24.0 (2024-04-23) {#0_24_0} -* Wrap chunk hash for install chunked code in a record and rename `storage_canister` to `store_canister`. -* Update subnet read state request conditions on requested paths. -* Fix: allow inter-canister calls (requests) to be spontaneously rejected in the abstract spec. - -### 0.23.0 (2024-03-06) {#0_23_0} -* The maximum length of a nonce in an ingress message is 32 bytes. -* Update specification of responses from the endpoint `/api/v2/status`. -* Stop canister calls might be rejected upon timeout. -* The IC sends a `user-agent` header with the value `ic/1.0` in canister HTTPS outcalls if the canister does not provide one. -* Add a management canister method for retrieving node metrics. -* Specify the resource reservation mechanism. -* Allow `in_replicated_execution` system API method to be executed during `canister_start`. -* Set the maximum depth of a delegation in a read_state response/certified variable certificate to 1. -* Canister version is guaranteed to increase if the canister's running status changes. -* Calls to frozen canisters are rejected with `SYS_TRANSIENT` instead of `CANISTER_ERROR`. -* Add API boundary nodes information into the certified state tree. - -### 0.22.0 (2023-11-15) {#0_22_0} -* Add metrics on subnet usage into the certified state tree and a new HTTP endpoint `/api/v2/subnet//read_state` for retrieving them. -* Add management canister methods to support installing large WebAssembly modules split into chunks. -* Add a system API method to determine if the canister is running in replicated or non-replicated mode. -* Add a system API method to burn cycles of the canister that calls this method. -* Add a check that a canister receiving an ingress message is Running before the ingress message is marked as Received. -* Increase the maximum number of globals in a canister's WASM. -* Add per-call context performance counter. -* Update the computation of the representation-independent hash for the case of maps with nested maps. -* Remove `senders` field from user delegations. - -### 0.21.0 (2023-09-18) {#0_21_0} -* Canister cycle balance cannot decrease below the freezing limit after executing `install_code` on the management canister. -* System API calls `ic0.msg_caller_size` and `ic0.msg_caller_copy` can be called in all contexts except for (start) function. -* Added note on confidentiality of values in the certified state tree. -* Update algorithm computing the request and response hash in the HTTP Gateway including clarification of when the HTTP Gateway can allow for arbitrary certification version in the canister's response. -* Update conditions on requested paths in HTTP read state requests. -* Added new query methods in the Bitcoin API. -* Added node public keys to certified state and node signatures to query call responses. -* Added a new mode for canister upgrades skipping pre-upgrade method's execution. - -### 0.20.0 (2023-07-11) {#0_20_0} -* IC Bitcoin API, ECDSA API, canister HTTPS outcalls API, and 128-bit cycles System API are considered stable. -* Add conditions on requested paths in read state requests. -* Add composite queries. -* Specify that the canister version is incremented upon every successful message execution except for successful message execution of a query method. - -### 0.19.0 (2023-06-08) {#0_19_0} -* canister version can be specified in some management canister calls (canister creation, canister code changes, canister settings changes) -* IC records canister history (canister creation, canister code changes, and canister controllers changes) -* added a new `canister_info` management canister call returning current module hash, current controllers, and canister history -* added a new system API call `ic0.is_controller` (checking if a principal is a controller of the canister) -* stable memory System API calls can be invoked in the WebAssembly module `(start)` function -* the system API call `ic0.global_timer_set` can be invoked in canister pre-upgrade -* added modeling WASM start function in the concrete `CanisterModule` specification -* WebAssembly module requirements have been revised (relaxed max number of declared functions and globals, added conditions on exported functions) -* certified variables are cleared if a canister is reinstalled -* a canister having an open call context marked as deleted cannot reach Stopped state -* a desired canister ID of the canister created by `provisional_create_canister_with_cycles` (in testing environments) can be specified using `specified_id` -* conditions on envelope delegations have been revised (relaxed max number of delegations, restricted max number of targets per delegation, forbidden cycles in the delegation chain) -* added a new optional field `senders` in envelope delegations (restricting users to which a delegation applies) -* all `/request_status/` paths must refer to the same `request_id` in a `read_state` request -* IC protocol execution error conditions (such as failing `inspect_message` method of a canister) are returned as 200 HTTP responses with a cbor body describing the error (instead of 4xx or 5xx HTTP responses) - -### 0.18.9 (2022-12-06) {#0_18_9} -* Global timers -* Canister version -* Clarifications for HTTP requests & Bitcoin integration costs - -### 0.18.8 (2022-11-09) {#0_18_8} -* Updated HTTP request API -* Canister status available to canister -* 64-bit stable memory is no longer experimental - -### 0.18.7 (2022-09-27) {#0_18_7} -* HTTP request API -* Reserved principals - -### 0.18.6 (2022-08-09) {#0_18_6} -* Canister access to performance metrics -* Query calls are rejected when the canister is frozen -* Support for implementation-specific error codes for requests -* Deleted call contexts do not prevent canister from reaching Stopped state -* Update effective canister id checks in certificate delegations -* Formal model in Isabelle - -### 0.18.5 (2022-07-08) {#0_18_5} -* Idle consumption of resources in cycles per day can be obtain via `canister_status` method of the management canister -* Include the HTTP Gateway Protocol in this spec -* Clarifications in definition of cycles consumption - -### 0.18.4 (2022-06-20) {#0_18_4} - -* Canister cycle balances are represented by 128 bits, and no system-defined upper limit exists anymore -* Canister modules can be gzip-encoded -* Expose Wasm custom sections in the state tree -* EXPERIMENTAL: Canister API for accessing Bitcoin transactions -* EXPERIMENTAL: Canister API for threshold ECDSA signatures - -### 0.18.3 (2022-01-10) {#0_18_3} - -* New System API which uses 128-bit values to represent the amount of cycles -* Subnet delegations include a canister id scope - -### 0.18.2 (2021-09-29) {#0_18_2} - -* Canister heartbeat -* Terminology changes -* Support for 64-bit stable memory - - -### 0.18.1 (2021-08-04) {#0_18_1} - -* Support RSA PKCS#1 v1.5 signatures in web authentication -* Spec clarification: Fix various typos and improve textual clarity - - -### 0.18.0 (2021-05-18) {#0_18_0} - -* A canister has a set of controllers, instead of always one - - -### 0.17.0 (2021-04-22) {#0_17_0} - -* Canister Signatures are introduced -* Spec clarification: the signature in the WebAuthn scheme is prefixed by the CBOR self-identifying tag -* Cycle-depleted canisters are forcibly uninstalled -* Canister settings in `create_canister` and `update_settings`. `install_code` no longer takes allocation settings. -* A freezing threshold can be configured via the canister settings - -### 0.16.1 (2021-04-14) {#0_16_1} -* The cleanup callback is introduced - -### 0.16.0 (2021-03-25) {#0_16_0} - -* New http v2 API that allows for stateless boundary nodes - -### 0.15.6 (2021-03-25) {#0_15_6} - -* The system may impose limits on the number of globals and functions -* No ingress messages towards empty canisters are accepted -* No ingress messages towards `raw_rand` and `deposit_cycles` are accepted -* A memory allocation of `0` means “best effort” - - -### 0.15.5 (2021-03-11) {#0_15_5} - -* deposit_cycles(): any caller allowed - - -### 0.15.4 (2021-03-04) {#0_15_4} - -* Ingress message filtering -* Add ECDSA signatures on curve secp256k1 -* Clarify that the `ic0.data_certificate_present` system function may be - called in all contexts. - - -### 0.15.3 (2021-02-26) {#0_15_3} - -* Expose module hash and controller via `read_state` - - -### 0.15.2 (2021-02-09) {#0_15_2} - -* The document is renamed to “Internet Computer Interface Spec” - - -### 0.15.0 (2020-12-17) {#0_15_0} - -* Support for raw Ed25519 keys is removed - - -### 0.14.1 (2020-12-08) {#0_14_1} - -* The default `memory_allocation` becomes unspecified - - -### 0.14.0 (2020-11-18) {#0_14_0} - -* Support for funds is scaled back to only support cycles -* The `ic0.msg_cycles_accept` system call now returns the actually accepted - cycles -* The `provisional_` management calls are introduced - - -### 0.13.2 (2020-11-12) {#0_13_2} - -* The `ic0.canister_status` system call - - -### 0.13.1 (2020-11-06) {#0_13_1} - -* Delegation between user public keys - - -### 0.13.0 (2020-10-19) {#0_13_0} - -* Certification (also removes “request-status” request) - - -### 0.12.2 (2020-10-23) {#0_12_2} - -* User authentication method based on WebAuthn is introduced -* User authentication can use ECDSA -* Public keys are DER-encoded - - -### 0.12.1 (2020-10-16) {#0_12_1} - -* Return more information in the `canister_status` management call - - -### 0.12.0 (2020-10-13) {#0_12_0} - -* Anonymous requests must have the sender field set - - -### 0.11.1 (2020-10-01) {#0_11_1} - -* The `deposit_funds` call - - -### 0.11.0 (2020-09-23) {#0_11_0} - -* Inter-canister calls are now performed using a builder-like API -* Support for funds (balances and transfers) - - -### 0.10.3 (2020-09-21) {#v0_10_3} - -* The anonymous user is introduced - - -### 0.10.1 (2020-09-01) {#v0_10_1} - -* Forward-port changes from 0.9.3 - - -### 0.10.0 (2020-08-06) {#v0_10_0} - -* Users can set/update a memory allocation when installing/upgrading a canister. -* The `expiry` field is added to requests - - -### 0.9.3 (2020-09-01) {#v0_9_3} - -* The management canister supports the `raw_rand` method - - -### 0.9.2 (2020-08-05) {#v0_9_2} - -* Canister controllers can stop/start canisters and can query their status. -* Canister controllers can delete canisters - - -### 0.9.1 (2020-07-20) {#v0_9_1} - -* Forward-port changes from 0.8.2 - - -### 0.9.0 (2020-07-15) {#v0_9_0} - -* Introduction of a domain separator (again) -* The calculation of “derived ids” has changed -* The self-authenticating and derived id forms use a truncated hash -* The textual representation of principals has changed - - -### 0.8.2 (2020-07-17) {#v0_8_2} - -* Installing code via `reinstall` works also on the empty canister - - -### 0.8.1 (2020-07-10) {#v0_8_1} - -* Reflect refined process in README and intro. -* `ic0.time` added - - -### 0.8.0 (2020-06-23) {#v0_8_0} - -* Revert the introduction of a domain separator - - -### 0.6.2 (2020-06-23) {#v0_6_2} - -* Fix meaning-changing typos in `ic.did` - - -### 0.6.0 (2020-06-08) {#v0_6_0} - -* Make all canister ids system-chosen -* HTTP requests for management features are removed - - -### 0.4.0 (2020-05-25) {#v0_4_0} - -* (editorial) the term “principal” is now used for the _id_ of a canister or - user, not the canister or user itself -* The signature of a request needs to be calculated using a domain separator -* Describe the `controller` attribute, add a request to change it -* The IC management canister is introduced - - -### 0.2.16 (2020-05-29) {#v0_2_16} - -* More tests about calls from query methods - - -### 0.2.14 (2020-05-14) {#v0_2_14} - -* Bugfix: Mode should be `reinstall`, not `replace` - - -### 0.2.8 (2020-04-23) {#v0_2_8} - -* Include section with CDDL description - - -### 0.2.4 (2020-03-23) {#v0_2_4} - -* simplify versioning (only three components), skip 0.2.2 to avoid confusion with 0.2.0.2 -* Clarification: `reply` field is always present -* General cleanup based on front-to-back reading - - -### 0.2.0.0 (2020-03-11) {#v0_2_0_0} - -* This is the first release. Subsequent releases will include a changelog. - - - diff --git a/docs/reference/application-canisters.md b/docs/references/application-canisters.md similarity index 100% rename from docs/reference/application-canisters.md rename to docs/references/application-canisters.md diff --git a/docs/reference/candid-spec.md b/docs/references/candid-spec.md similarity index 100% rename from docs/reference/candid-spec.md rename to docs/references/candid-spec.md diff --git a/docs/reference/chain-key-canister-ids.md b/docs/references/chain-key-canister-ids.md similarity index 100% rename from docs/reference/chain-key-canister-ids.md rename to docs/references/chain-key-canister-ids.md diff --git a/docs/reference/cycles-costs.md b/docs/references/cycles-costs.md similarity index 100% rename from docs/reference/cycles-costs.md rename to docs/references/cycles-costs.md diff --git a/docs/reference/developer-tools.md b/docs/references/developer-tools.md similarity index 100% rename from docs/reference/developer-tools.md rename to docs/references/developer-tools.md diff --git a/docs/reference/digital-asset-standards.md b/docs/references/digital-asset-standards.md similarity index 100% rename from docs/reference/digital-asset-standards.md rename to docs/references/digital-asset-standards.md diff --git a/docs/reference/execution-errors.md b/docs/references/execution-errors.md similarity index 97% rename from docs/reference/execution-errors.md rename to docs/references/execution-errors.md index 60f5360..5be01dc 100644 --- a/docs/reference/execution-errors.md +++ b/docs/references/execution-errors.md @@ -38,7 +38,7 @@ The canister aborted execution by calling the `ic0.trap` system API. Canister called `ic0.trap` with message: ``` -When encountering an error, canisters may choose to fail with an error message by calling the [`ic0.trap` API](./ic-interface-spec.md). The Rust and Motoko CDKs insert calls to this API when panicking. +When encountering an error, canisters may choose to fail with an error message by calling the [`ic0.trap` API](./ic-interface-spec/canister-interface.md). The Rust and Motoko CDKs insert calls to this API when panicking. To fix this error, test the canister to determine which inputs trigger panics. Review the error message embedded in the trap to identify the source of the failure. @@ -105,7 +105,7 @@ Canister cannot grow its memory usage. ICP imposes limits on both the main memory (Wasm heap, up to 4 GiB for wasm32 or 6 GiB for wasm64) and stable memory (up to 500 GiB) per canister, as well as on the total memory per subnet. This error is triggered when any one of those limits is reached. -To diagnose this error, check the canister's current memory usage using the [`canister_status` API](./ic-interface-spec.md) or the `icp canister status` command. Subnet memory usage is visible on the [ICP dashboard](https://dashboard.internetcomputer.org/subnets). +To diagnose this error, check the canister's current memory usage using the [`canister_status` API](./ic-interface-spec/management-canister.md#ic-canister_status) or the `icp canister status` command. Subnet memory usage is visible on the [ICP dashboard](https://dashboard.internetcomputer.org/subnets). To fix this error: @@ -274,7 +274,7 @@ The canister tried to use a system API call in a message type where it is not pe Canister violated contract: "ic0.call_new" cannot be executed in non-replicated query mode. ``` -Certain system APIs are only available in specific message types. For a complete overview of which system APIs are available in which contexts, see the [IC interface specification](./ic-interface-spec.md). +Certain system APIs are only available in specific message types. For a complete overview of which system APIs are available in which contexts, see the [IC interface specification](./ic-interface-spec/canister-interface.md#system-api-imports). Common cases: - Trying to make a call in a query (only allowed in composite queries). @@ -313,7 +313,7 @@ The canister tried to set certified data that exceeds the ICP limit on certified Canister violated contract: ic0_certified_data_set failed because the passed data must be no larger than 32 bytes. Found 100 bytes. ``` -The certified data field is limited to 32 bytes. To fix this error, certify just a hash of the data instead of its full contents. For background on certified data, see the [IC interface specification](./ic-interface-spec.md). +The certified data field is limited to 32 bytes. To fix this error, certify just a hash of the data instead of its full contents. For background on certified data, see the [IC interface specification](./ic-interface-spec/canister-interface.md#system-api-certified-data). ### Canister made a call with too large a method name @@ -378,7 +378,7 @@ Attempted to execute a message, but the canister contains no Wasm module. A canister can exist without having Wasm code installed if it has never been deployed or if it has been uninstalled. Check the canister status using `icp canister status` or the `canister_status` API to confirm whether a module is installed (the "Module hash" field will be non-null if code is present). -To fix this error, deploy code to the canister using `icp deploy` or the [`install_code`](./ic-interface-spec.md) / [`install_chunked_code`](./ic-interface-spec.md) management canister APIs. +To fix this error, deploy code to the canister using `icp deploy` or the [`install_code`](./ic-interface-spec/management-canister.md#ic-install_code) / [`install_chunked_code`](./ic-interface-spec/management-canister.md#ic-install_chunked_code) management canister APIs. ### Wasm module too large @@ -809,6 +809,6 @@ To fix this error, confirm that the metadata section exists for the given canist - Review [resource limits](./cycles-costs.md) for the full table of ICP constraints. - Learn about [canister lifecycle](../guides/canister-management/lifecycle.md) including traps during upgrades. - Optimize resource usage with the [canister optimization guide](../guides/canister-management/optimization.md). -- Understand the system APIs in the [IC interface specification](./ic-interface-spec.md). +- Understand the system APIs in the [IC interface specification](./ic-interface-spec/canister-interface.md). diff --git a/docs/reference/glossary.md b/docs/references/glossary.md similarity index 99% rename from docs/reference/glossary.md rename to docs/references/glossary.md index 5ee2a45..e456386 100644 --- a/docs/reference/glossary.md +++ b/docs/references/glossary.md @@ -121,7 +121,7 @@ A **canister signature** uses a signature scheme based on [certified variables]( [canister id](#canister-identifier) plus a seed (so that every [canister](#canister) has many public keys); signatures are certificates that prove that the canister has put the signed message -at a specific place in its state tree. Details can be found in the [Internet Computer interface specification](./ic-interface-spec.md). +at a specific place in its state tree. Details can be found in the [Internet Computer interface specification](./ic-interface-spec/index.md#canister-signatures). #### canister state @@ -292,7 +292,7 @@ An **identity** is a byte string that is used to identify an entity, such as a [principal](#principal), that interacts with the [Internet Computer](#internet-computer-protocol-icp). For users, the identity is the SHA-224 hash of the DER-encoded public key of the user. -[IC interface specification](./ic-interface-spec.md) has more +[IC interface specification](./ic-interface-spec/index.md) has more detail. #### Internet Identity @@ -651,6 +651,6 @@ stack-based virtual machine. diff --git a/docs/reference/http-gateway-spec.md b/docs/references/http-gateway-spec.md similarity index 91% rename from docs/reference/http-gateway-spec.md rename to docs/references/http-gateway-spec.md index 07a3da0..e67ece8 100644 --- a/docs/reference/http-gateway-spec.md +++ b/docs/references/http-gateway-spec.md @@ -7,7 +7,7 @@ sidebar: ## Introduction -The HTTP Gateway Protocol is an extension of the Internet Computer Protocol that allows conventional HTTP clients to interact with the Internet Computer network. This is important for software such as web browsers to be able to fetch and render client-side canister code, including HTML, CSS, and JavaScript as well as other static assets such as images or videos. The HTTP Gateway does this by translating between standard HTTP requests and [API canister calls](./ic-interface-spec.md#http-interface) that the Internet Computer Protocol will understand. +The HTTP Gateway Protocol is an extension of the Internet Computer Protocol that allows conventional HTTP clients to interact with the Internet Computer network. This is important for software such as web browsers to be able to fetch and render client-side canister code, including HTML, CSS, and JavaScript as well as other static assets such as images or videos. The HTTP Gateway does this by translating between standard HTTP requests and [API canister calls](./ic-interface-spec/https-interface.md#http-interface) that the Internet Computer Protocol will understand. Such an HTTP Gateway could be a stand-alone proxy, it could be implemented in web browsers (natively, via a plugin or a service worker) or in other ways. This document describes the interface and semantics of this protocol independent of a concrete HTTP Gateway so that all HTTP Gateway Protocol implementations can be compatible. @@ -70,7 +70,7 @@ The full [Candid](https://github.com/dfinity/candid/blob/master/spec/Candid.md) ## Query Calls -The encoded HTTP request is sent as a query call according to the [HTTPS Interface](/references/ic-interface-spec#http-query) via the API Boundary Node resolved according to [API Boundary Node Resolution](#api-boundary-node-resolution). +The encoded HTTP request is sent as a query call according to the [HTTPS Interface](./ic-interface-spec/https-interface.md#http-query) via the API Boundary Node resolved according to [API Boundary Node Resolution](#api-boundary-node-resolution). ## HTTP Response Decoding @@ -106,7 +106,7 @@ Notes: ## Response Verification -The HTTP Gateway will primarily be used to load static assets needed to run frontend canister code, so both low latency and security are essential for providing a good experience to end users. [Query calls](./ic-interface-spec.md#http-query) are more performant but less secure than [Update calls](./ic-interface-spec.md#http-call). +The HTTP Gateway will primarily be used to load static assets needed to run frontend canister code, so both low latency and security are essential for providing a good experience to end users. [Query calls](./ic-interface-spec/https-interface.md#http-query) are more performant but less secure than [Update calls](./ic-interface-spec/https-interface.md#http-call). Response verification fills the security gap left by query calls. It is a versioned subprotocol that allows for an HTTP Gateway to verify a certified response received as a result of performing a query call to the Internet Computer. Two versions are currently supported, the current version of response verification is covered in this section and the legacy version is covered in [another section](#legacy-response-verification). The legacy version only includes a mapping of the request URL to the response body so it is quite limiting in what it can verify. The current version builds on the legacy version by optionally including the following extra parameters in the certification process: @@ -147,8 +147,8 @@ Response verification fills the security gap left by query calls. It is a versio The `IC-Certificate` header is a structured header according to [RFC 8941](https://www.rfc-editor.org/rfc/rfc8941.html) with the following mandatory fields: -- `certificate`: [Base64 encoded](https://www.rfc-editor.org/rfc/rfc4648#section-4) string of self-describing, [CBOR-encoded](https://www.rfc-editor.org/rfc/rfc8949.html) bytes that decode into a valid [certificate](./ic-interface-spec.md#certification). -- `tree`: [Base64 encoded](https://www.rfc-editor.org/rfc/rfc4648#section-4) string of self-describing, [CBOR-encoded](https://www.rfc-editor.org/rfc/rfc8949.html) bytes that decode into a valid hash tree as per [certificate encoding](./ic-interface-spec.md#certification-encoding). +- `certificate`: [Base64 encoded](https://www.rfc-editor.org/rfc/rfc4648#section-4) string of self-describing, [CBOR-encoded](https://www.rfc-editor.org/rfc/rfc8949.html) bytes that decode into a valid [certificate](./ic-interface-spec/certification.md#certification). +- `tree`: [Base64 encoded](https://www.rfc-editor.org/rfc/rfc4648#section-4) string of self-describing, [CBOR-encoded](https://www.rfc-editor.org/rfc/rfc8949.html) bytes that decode into a valid hash tree as per [certificate encoding](./ic-interface-spec/certification.md#certification-encoding). The following additional fields are mandatory for response verification version 2 and upwards: @@ -163,13 +163,13 @@ The decoded `expr_path` field of [The Certificate Header](#the-certificate-heade - The last segment is always `<$>` or `<*>`. - No segment, aside from the last segment, will be `<$>` or `<*>`. - Each segment between `http_expr` and `<$>` or `<*>` will contain a [percent-encoded](https://www.rfc-editor.org/rfc/rfc3986#section-2) segment of the current request URL. -- The path must be the most specific path for the current request URL in the tree, i.e. a lookup of more specific paths must return `Absent` as per [lookup](./ic-interface-spec.md#lookup). +- The path must be the most specific path for the current request URL in the tree, i.e. a lookup of more specific paths must return `Absent` as per [lookup](./ic-interface-spec/certification.md#lookup). - An `expr_path` that ends in `<$>` is an exact match for the current request URL. - `<*>` is treated as a wildcard, so an `expr_path` that ends in `<*>` is a partial match for the current request URL. ### Certificate Validation -Certificate validation is performed as part of [response verification](#response-verification) as per [Canister Signatures](./ic-interface-spec.md#canister-signatures) and [Certification](./ic-interface-spec.md#certificate). It is expanded on here concerning [response verification](#response-verification) for completeness: +Certificate validation is performed as part of [response verification](#response-verification) as per [Canister Signatures](./ic-interface-spec/index.md#canister-signatures) and [Certification](./ic-interface-spec/certification.md#certification). It is expanded on here concerning [response verification](#response-verification) for completeness: 1. Case-insensitive search for a response header called `IC-Certificate`. 2. The value of the header corresponds to the format described in [the certificate header](#the-certificate-header) section. @@ -177,8 +177,8 @@ Certificate validation is performed as part of [response verification](#response - The certificate is signed by the root key of the NNS subnet or by a subnet delegation signed by that same root key. - If the certificate contains a subnet delegation, the delegation must be valid for the given canister. - The timestamp at the `/time` path must be recent, e.g. 5 minutes. - - The subnet state tree in the certificate must reveal the canister's [certified data](./ic-interface-spec.md#system-api-certified-data). -4. The root hash of the decoded `tree` must match the canister's [certified data](./ic-interface-spec.md#system-api-certified-data). + - The subnet state tree in the certificate must reveal the canister's [certified data](./ic-interface-spec/canister-interface.md#system-api-certified-data). +4. The root hash of the decoded `tree` must match the canister's [certified data](./ic-interface-spec/canister-interface.md#system-api-certified-data). ### The Certificate Expression Header @@ -280,7 +280,7 @@ Implementors should note that the EBNF specification does not allow for any whit The request hash is calculated as follows: -1. Let `request_headers_hash` be the [representation-independent hash](/references/ic-interface-spec#hash-of-map) of the request headers: +1. Let `request_headers_hash` be the [representation-independent hash](./ic-interface-spec/https-interface.md#hash-of-map) of the request headers: - The header names are lower-cased. - Only include headers listed in the `certified_request_headers` field of [the certificate expression header](#the-certificate-expression-header). - If the field is empty or no value was supplied, no headers are included. @@ -298,7 +298,7 @@ The request hash is calculated as follows: The response hash is calculated as follows: -1. Let `response_headers_hash` be the [representation-independent hash](/references/ic-interface-spec#hash-of-map) of the response headers: +1. Let `response_headers_hash` be the [representation-independent hash](./ic-interface-spec/https-interface.md#hash-of-map) of the response headers: - The header names are lower-cased. - The `IC-Certificate` header is always excluded. - The `IC-CertificateExpression` header is always included. @@ -339,7 +339,7 @@ The type of the token value is chosen by the canister; the HTTP Gateway obtains ## Upgrade to Update Calls -If the canister sets `upgrade = opt true` in the `HttpResponse` reply from the `http_request` call, then the HTTP Gateway ignores all other fields of the response. The HTTP Gateway performs an [update](/references/ic-interface-spec#http-call) call to `http_request_update`, passing an `HttpUpdateRequest` record as the argument, and uses the resulting response from `http_request_update` instead. The `HttpUpdateRequest` record is identical to the original `HttpRequest`, with the `certificate_version` field excluded. +If the canister sets `upgrade = opt true` in the `HttpResponse` reply from the `http_request` call, then the HTTP Gateway ignores all other fields of the response. The HTTP Gateway performs an [update](./ic-interface-spec/https-interface.md#http-call) call to `http_request_update`, passing an `HttpUpdateRequest` record as the argument, and uses the resulting response from `http_request_update` instead. The `HttpUpdateRequest` record is identical to the original `HttpRequest`, with the `certificate_version` field excluded. The value of the `upgrade` field returned from `http_request_update` is ignored. @@ -368,7 +368,7 @@ The steps for response verification are as follows: ## Response Verification Version Assertion -Canisters can report the supported versions of response verification using (public) metadata sections available in the [system state tree](./ic-interface-spec.md#state-tree-canister-information). This metadata will be read by the HTTP Gateway using a [read_state request](./ic-interface-spec.md). The metadata section must be a (public) custom section with the name `supported_certificate_versions` and contain a comma-delimited string of versions, e.g., `1,2`. This is treated as an optional, additional layer of security for canisters supporting multiple versions. If the metadata has not been added (i.e., the `read_state` request _succeeds_ and the lookup of the metadata section in the `read_state` response certificate returns `Absent`), then the HTTP Gateway will allow for whatever version the canister has responded with. +Canisters can report the supported versions of response verification using (public) metadata sections available in the [system state tree](./ic-interface-spec/index.md#state-tree-canister-information). This metadata will be read by the HTTP Gateway using a [read_state request](./ic-interface-spec/https-interface.md#http-read-state). The metadata section must be a (public) custom section with the name `supported_certificate_versions` and contain a comma-delimited string of versions, e.g., `1,2`. This is treated as an optional, additional layer of security for canisters supporting multiple versions. If the metadata has not been added (i.e., the `read_state` request _succeeds_ and the lookup of the metadata section in the `read_state` response certificate returns `Absent`), then the HTTP Gateway will allow for whatever version the canister has responded with. The request for the metadata will only be made by the HTTP Gateway if there is a downgrade. If the HTTP Gateway requests v2 and the canister responds with v2, then a request will not be made. If the HTTP Gateway requests v2 and the canister responds with v1, a request will be made. If a request is made, the HTTP Gateway will not accept any response from the canister that is below the max version supported by both the HTTP Gateway and the canister. This will guarantee that a canister supporting both v1 and v2 will always have v2 security when accessed by an HTTP Gateway that supports v2. @@ -462,7 +462,7 @@ type HttpRequest = record { ### Upgrade to Update Calls Interface -The `http_request_update` method of the `service` interface along with the `upgrade` field of the `HttpResponse` interface is optional depending on whether the canister needs to use the [upgrade to update calls](#upgrade-to-update-calls) feature. Not that the `HttpUpdateRequest` type is the same as the `HttpRequest` type, but excludes the `certificate_version` field since this should not affect the response to an [update](/references/ic-interface-spec#http-call) call from a canister. +The `http_request_update` method of the `service` interface along with the `upgrade` field of the `HttpResponse` interface is optional depending on whether the canister needs to use the [upgrade to update calls](#upgrade-to-update-calls) feature. Not that the `HttpUpdateRequest` type is the same as the `HttpRequest` type, but excludes the `certificate_version` field since this should not affect the response to an [update](./ic-interface-spec/https-interface.md#http-call) call from a canister. ``` type HttpUpdateRequest = record { @@ -540,17 +540,17 @@ service : { diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md new file mode 100644 index 0000000..4d8867e --- /dev/null +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -0,0 +1,5760 @@ +--- +title: "Abstract Behavior" +description: "Formal specification of the Internet Computer abstract state machine and execution semantics" +sidebar: + label: "Abstract Behavior" + order: 5 +--- + +:::note +This section is a rigorous formal specification intended for protocol implementors and security researchers. Most application developers do not need to read this section. See the [HTTPS Interface](./https-interface.md), [Canister Interface](./canister-interface.md), and [IC Management Canister](./management-canister.md) pages instead. +::: + +## Abstract behavior {#abstract-behavior} + +The previous sections describe the interfaces, i.e. outer edges of the Internet Computer, but give only intuitive and vague information in prose about what these interfaces actually do. + +The present section aims to address that question with great precision, by describing the *abstract state* of the whole Internet Computer, and how this state can change in response to API function calls, or spontaneously (modeling asynchronous, distributed or non-deterministic execution). + +The design of this abstract specification (e.g. how and where pending messages are stored) are *not* to be understood to in any way prescribe a concrete implementation or software architecture. The goals here are formal precision and clarity, but not implementability, so this can lead to different ways of phrasing. + +### Notation + +We specify the behavior of the Internet Computer using ad-hoc pseudocode. + +The manipulated values are primitive values (numbers, text, binary blobs), aggregate values (lists, unordered lists a.k.a. bags, partial maps, records with fixed fields, named constructors) and functions. + +We use a concatenation operator `·` with various types: to extend sets and maps, or to concatenate lists with lists or lists with elements. + +The shape of values is described using a hand-wavy type system. We use `Foo = Nat` to define type aliases; now `Foo` can be used instead of `Nat`. Often, the right-hand side is a more complex type here, e.g. a record, or multiple possible types separated by a vertical bar (`|`). Partial maps are written as `Key ↦ Value` and the function type as `Argument → Result`. + +:::note + +All values are immutable! State change is specified by describing the new state, not by changing the existing state. + +::: + +Record fields are accessed using dot-notation (e.g. `S.request_id > 0`). To create a new record from an existing record `R` with some fields changed, the syntax `R where field = new_value` is used. This syntax can also be used to create new records with some deeply nested field changed: `R where some_map[key].field = new_value`. + +In the state transitions, upper-case variables (`S`, `C`, `Req_id`) are free variables: The state transition may be taken for any possible value of these variables. `S` always refers to the previous state. A state transition often comes with a list of *conditions*, which may restrict the values of these free variables. The *state after* is usually described using the record update syntax by starting with `S where`. + +For example, the condition `S.messages = Older_messages · M · Younger_messages` says that `M` is some message in field `messages` of the record `S`, and that `Younger_messages` and `Older_messages` are the other messages in the state. If the "state after" specifies `S with messages = Older_messages · Younger_messages`, then the message `M` is removed from the state. + +### Abstract state + +In this specification, we describe the Internet Computer as a state machine. In particular, there is a single piece of data that describes the complete state of the IC, called `S`. + +Of course, this is a huge simplification: The real Internet Computer is distributed and has a multi-component architecture, and the state is spread over many different components, some physically separated. But this simplification allows us to have a concise description of the behavior, and to easily make global decisions (such as, "is there any pending message"), without having to specify the bookkeeping that allows such global decisions. + +#### Identifiers + +Principals (canister ids and user ids) are blobs, but some of them have special form, as explained in [Special forms of Principals](./index.md#id-classes). +``` +type Principal = Blob +``` +The function +``` +mk_self_authenticating_id : PublicKey -> Principal +mk_self_authenticating_id pk = H(pk) · 0x02 +``` +calculates self-authenticating ids. + +The function +``` +canister_signature_pk : Principal -> Blob -> PublicKey +``` +calculates the public key of a [canister signature](./index.md#canister-signatures). + +The function +``` +mk_derived_id : Principal -> Blob -> Principal +mk_derived_id p nonce = H(|p| · p · nonce) · 0x03 +``` +calculates derived ids. With `|p|` we denote the length of the principal, in bytes, encoded as a single byte. + +The principal of the anonymous user is fixed: +``` +anonymous_id : Principal +anonymous_id = 0x04 +``` +The principal of the management canister is the empty blob (i.e. `aaaaa-aa`): +``` +ic_principal : Principal = "" +``` +These function domains and fixed values are mutually disjoint. + +Method names can be arbitrary pieces of text: +``` +MethodName = Text +``` +#### Abstract canisters {#abstract-canisters} + +The [WebAssembly System API](./canister-interface.md#system-api) is relatively low-level, and some of its details (e.g. that the argument data is queried using separate calls, and that closures are represented by a function pointer and a number, that method names need to be mangled) would clutter this section. Therefore, we abstract over the WebAssembly details as follows: + +- The state `WasmState` of a WebAssembly module is represented by its WASM (a.k.a. heap) and stable memory and a list of (exported or mutable) globals. For notational simplicity, the principal of the canister with state represented by `WasmState` is also stored in `WasmState`. + +- A canister module `CanisterModule` consists of an initial state, and (pure) functions that model function invocation on the canister. A function return value either indicates that the canister function traps, or returns a new state together with a description of the invoked asynchronous System API calls. + ``` + WasmState = { + wasm_memory : Blob; + stable_memory : Blob; + globals : [Global]; + self_id : Principal; + } + Global + = I32(Int) + | I64(Int) + | F32(Real) + | F64(Real) + | V128(Nat); + Callback = (abstract) + ChunkStore = Hash -> Blob + + Arg = Blob; + CallerId = Principal; + CallerInfoData = Blob; + CallerInfoSigner = Blob; + + Timestamp = Nat; + CanisterVersion = Nat; + Env = { + time : Timestamp; + controllers : List Principal; + global_timer : Nat; + balance : Nat; + reserved_balance : Nat; + reserved_balance_limit : Nat; + compute_allocation : Nat; + memory_allocation : Nat; + memory_usage_raw_module : Nat; + memory_usage_canister_history : Nat; + memory_usage_chunk_store : Nat; + memory_usage_snapshots : Nat; + freezing_threshold : Nat; + subnet_id : Principal; + subnet_size : Nat; + certificate : NoCertificate | Blob; + status : Running | Stopping | Stopped; + canister_version : CanisterVersion; + } + + RejectCode = Nat + Response = Reply Blob | Reject (RejectCode, Text) + MethodCall = { + callee : CanisterId; + method_name: MethodName; + arg: Blob; + transferred_cycles: Nat; + callback: Callback; + timeout_seconds : NoTimeout | Nat; + } + + UpdateFunc = WasmState -> Trap { cycles_used : Nat; } | Return { + new_state : WasmState; + new_calls : List MethodCall; + new_certified_data : NoCertifiedData | Blob; + new_global_timer : NoGlobalTimer | Nat; + response : NoResponse | Response; + cycles_accepted : Nat; + cycles_used : Nat; + } + QueryFunc = WasmState -> Trap { cycles_used : Nat; } | Return { + response : Response; + cycles_accepted : Nat; + cycles_used : Nat; + } + CompositeQueryFunc = WasmState -> Trap { cycles_used : Nat; } | Return { + new_state : WasmState; + new_calls : List MethodCall; + response : NoResponse | Response; + cycles_used : Nat; + } + SystemTaskFunc = WasmState -> Trap { cycles_used : Nat; } | Return { + new_state : WasmState; + new_calls : List MethodCall; + new_certified_data : NoCertifiedData | Blob; + new_global_timer : NoGlobalTimer | Nat; + cycles_used : Nat; + } + + AvailableCycles = Nat + RefundedCycles = Nat + + CanisterModule = { + initial_globals : [Global]; + init : (CanisterId, Arg, CallerId, Env) -> Trap { cycles_used : Nat; } | Return { + new_state : WasmState; + new_certified_data : NoCertifiedData | Blob; + new_global_timer : NoGlobalTimer | Nat; + cycles_used : Nat; + } + pre_upgrade : (WasmState, Principal, Env) -> Trap { cycles_used : Nat; } | Return { + new_state : WasmState; + new_certified_data : NoCertifiedData | Blob; + cycles_used : Nat; + } + post_upgrade : (WasmState, Arg, CallerId, Env) -> Trap { cycles_used : Nat; } | Return { + new_state : WasmState; + new_certified_data : NoCertifiedData | Blob; + new_global_timer : NoGlobalTimer | Nat; + cycles_used : Nat; + } + update_methods : MethodName ↦ ((Arg, CallerId, CallerInfoData, CallerInfoSigner, Deadline, Env, AvailableCycles) -> UpdateFunc) + query_methods : MethodName ↦ ((Arg, CallerId, CallerInfoData, CallerInfoSigner, Env) -> QueryFunc) + composite_query_methods : MethodName ↦ ((Arg, CallerId, CallerInfoData, CallerInfoSigner, Env) -> CompositeQueryFunc) + heartbeat : (Env) -> SystemTaskFunc + global_timer : (Env) -> SystemTaskFunc + on_low_wasm_memory : (Env) -> SystemTaskFunc + callbacks : (Callback, CallerId, CallerInfoData, CallerInfoSigner, Response, Deadline, RefundedCycles, Env, AvailableCycles) -> UpdateFunc + composite_callbacks : (Callback, CallerId, CallerInfoData, CallerInfoSigner, Response, Env) -> UpdateFunc + inspect_message : (MethodName, WasmState, Arg, CallerId, CallerInfoData, CallerInfoSigner, Env) -> Trap | Return { + status : Accept | Reject; + } + } + ``` + +This high-level interface presents a pure, mathematical model of a canister, and hides the bookkeeping required to provide the System API as seen in Section [Canister interface (System API)](./canister-interface.md#system-api). + +The `CanisterId` parameter of `init` is merely passed through to the canister, via the `canister.self` system call. + +The `Env` parameter provides synchronous read-only access to portions of the system state and canister metadata that are always available. + +The parsing of a blob to a canister module and its public and private custom sections is modelled via the (possibly implicitly failing) functions +``` +parse_wasm_mod : Blob -> CanisterModule +parse_public_custom_sections : Blob -> Text ↦ Blob +parse_private_custom_sections : Blob -> Text ↦ Blob +``` + +The concrete mapping of this abstract `CanisterModule` to actual WebAssembly concepts and the System API is described separately in section [Abstract Canisters to System API](#concrete-canisters). + +#### Call contexts + +The Internet Computer provides certain messaging guarantees: If a user or a canister calls another canister, it will eventually get a single response (a reply or a rejection), even if some canister code along the way fails. + +To ensure that only one response is generated, and also to detect when no response can be generated any more, the IC maintains a *call context*. The `needs_to_respond` field is set to `false` once the call has received a response. Further attempts to respond will now fail. +``` +Request = { + nonce : Blob; + ingress_expiry : Nat; + sender : UserId; + sender_info : { + info : Blob; + signer : Blob; + sig : Blob; + }; + canister_id : CanisterId; + method_name : Text; + arg : Blob; + } +CallId = (abstract) +CallOrigin + = FromUser { + request : Request; + } + | FromCanister { + calling_context : CallId; + callback: Callback; + deadline : NoDeadline | Timestamp | Expired Timestamp; + } + | FromSystemTask +CallCtxt = { + canister : CanisterId; + origin : CallOrigin; + needs_to_respond : bool; + deleted : bool; + available_cycles : Nat; +} +``` +#### Calls and Messages + +Calls into and within the IC are implemented as messages passed between canisters. During their lifetime, messages change shape: they begin as a call to a public method, which is resolved to a WebAssembly function that is then executed, potentially generating a response which is then delivered. + +Therefore, a message can have different shapes: +``` +Queue = Unordered | Queue { from : System | CanisterId; to : CanisterId } +EntryPoint + = PublicMethod MethodName Principal Blob + | Callback Callback Response RefundedCycles + | Heartbeat + | GlobalTimer + | OnLowWasmMemory +Message + = CallMessage { + origin : CallOrigin; + caller : Principal; + caller_info_data : Blob; + caller_info_signer : Blob; + callee : CanisterId; + method_name : Text; + arg : Blob; + transferred_cycles : Nat; + queue : Queue; + } + | FuncMessage { + call_context : CallId; + caller : Principal; + caller_info_data : Blob; + caller_info_signer : Blob; + receiver : CanisterId; + entry_point : EntryPoint; + queue : Queue; + } + | ResponseMessage { + origin : CallOrigin; + response : Response; + refunded_cycles : Nat; + } +``` + +The `queue` field is used to describe the message ordering behavior. Its concrete value is only used to determine when the relative order of two messages must be preserved, and is otherwise not interpreted. Response messages are not ordered so they have no `queue` field. + +A reference implementation would likely maintain a separate list of `messages` for each such queue to efficiently find eligible messages; this document uses a single global list for a simpler and more concise system state. + +#### API requests + +We distinguish between API requests (type `Request`) passed to `/api/v2/…/call` and `/api/v4/…/call`, which may be present in the IC state, and the *read-only* API requests passed to `/api/v3/…/read_state` and `/api/v3/…/query`, which are only ephemeral. + +These are the read-only messages: +``` +Path = List(Blob) +APIReadRequest + = StateRead = { + nonce : Blob; + ingress_expiry : Nat; + sender : UserId; + paths : List(Path); + } + | CanisterQuery = { + nonce : Blob; + ingress_expiry : Nat; + sender : UserId; + sender_info : { + info : Blob; + signer : Blob; + sig : Blob; + }; + canister_id : CanisterId; + method_name : Text; + arg : Blob; + } +``` + +Signed delegations contain the (unsigned) delegation data in a nested record, next to the signature of that data. +``` +PublicKey = Blob +Signature = Blob +SignedDelegation = { + delegation : { + pubkey : PublicKey; + targets : [CanisterId] | Unrestricted; + expiration : Timestamp + }; + signature : Signature +} +``` + +For the signatures in a `Request`, we assume that the following function implements signature verification as described in [Authentication](./https-interface.md#authentication). This function picks the corresponding signature scheme according to the DER-encoded metadata in the public key. +``` +verify_signature : PublicKey -> Signature -> Blob -> Bool +Envelope = { + content : Request | APIReadRequest; + sender_pubkey : PublicKey | NoPublicKey; + sender_sig : Signature | NoSignature; + sender_delegation: [SignedDelegation] +} +``` + +The evolution of a `Request` goes through these states, as explained in [Overview of canister calling](./https-interface.md#http-call-overview): +``` +RequestStatus + = Received + | Processing + | Rejected (RejectCode, Text) + | Replied Blob + | Done +``` + +A `Path` may refer to a request by way of a *request id*, as specified in [Request ids](./https-interface.md#request-id): +``` +RequestId = { b ∈ Blob | |b| = 32 } +hash_of_map: Request -> RequestId +``` + +#### The system state + +Finally, we can describe the state of the IC as a record having the following fields: +``` +CanState + = EmptyCanister | { + wasm_state : WasmState; + module : CanisterModule; + raw_module : Blob; + public_custom_sections: Text ↦ Blob; + private_custom_sections: Text ↦ Blob; +} +CanStatus + = Running + | Stopping (List (CallOrigin, Nat)) + | Stopped +ChangeOrigin + = FromUser { + user_id : PrincipalId; + } + | FromCanister { + canister_id : PrincipalId; + canister_version : CanisterVersion | NoCanisterVersion; + } +CodeDeploymentMode + = Install + | Reinstall + | Upgrade +SnapshotId = (abstract) +SnapshotSource + = TakenFromCanister + | MetadataUpload +ChangeDetails + = Creation { + controllers : [PrincipalId]; + environment_variables_hash: opt Blob; + } + | CodeUninstall + | CodeDeployment { + mode : CodeDeploymentMode; + module_hash : Blob; + } + | LoadSnapshot { + from_canister_id : PrincipalId; + snapshot_id : SnapshotId; + canister_version : CanisterVersion; + taken_at_timestamp : Timestamp; + source : SnapshotSource; + } + | ControllersChange { + controllers: [PrincipalId]; + } + | RenameCanister { + canister_id : PrincipalId; + total_num_changes : Nat; + rename_to : { + canister_id : PrincipalId; + version : Nat; + total_num_changes : Nat; + }; + requested_by : PrincipalId; + } +Change = { + timestamp_nanos : Timestamp; + canister_version : CanisterVersion; + origin : ChangeOrigin; + details : ChangeDetails; +} +CanisterHistory = { + total_num_changes : Nat; + recent_changes : [Change]; +} +CanisterLogVisibility + = Controllers + | Public + | AllowedViewers [Principal] +CanisterSnapshotVisibility + = Controllers + | Public + | AllowedViewers [Principal] +CanisterLog = { + idx : Nat; + timestamp_nanos : Nat; + content : Blob; +} +OnLowWasmMemoryHookStatus + = ConditionNotSatisfied + | Ready + | Executed +QueryStats = { + timestamp : Timestamp; + num_instructions : Nat; + request_payload_bytes : Nat; + response_payload_bytes : Nat; +} +Subnet = { + subnet_id : Principal; + subnet_size : Nat; +} +Snapshot = { + source : SnapshotSource; + taken_at_timestamp : Timestamp; + raw_module : Blob; + wasm_state : WasmState; + chunk_store : ChunkStore; + canister_version : CanisterVersion; + certified_data : Blob; + global_timer : Timestamp | null; + on_low_wasm_memory_hook_status : OnLowWasmMemoryHookStatus | null; +} +S = { + requests : Request ↦ (RequestStatus, Principal); + canisters : CanisterId ↦ CanState; + snapshots: CanisterId ↦ SnapshotId ↦ Snapshot; + controllers : CanisterId ↦ Set Principal; + compute_allocation : CanisterId ↦ Nat; + memory_allocation : CanisterId ↦ Nat; + freezing_threshold : CanisterId ↦ Nat; + canister_status: CanisterId ↦ CanStatus; + canister_version: CanisterId ↦ CanisterVersion; + canister_subnet : CanisterId ↦ Subnet; + time : CanisterId ↦ Timestamp; + global_timer : CanisterId ↦ Timestamp; + balances: CanisterId ↦ Nat; + reserved_balances: CanisterId ↦ Nat; + reserved_balance_limits: CanisterId ↦ Nat; + wasm_memory_limit: CanisterId ↦ Nat; + wasm_memory_threshold: CanisterId ↦ Nat; + environment_variables: CanisterId ↦ (Text ↦ Text) + on_low_wasm_memory_hook_status: CanisterId ↦ OnLowWasmMemoryHookStatus; + certified_data: CanisterId ↦ Blob; + canister_history: CanisterId ↦ CanisterHistory; + canister_log_visibility: CanisterId ↦ CanisterLogVisibility; + canister_snapshot_visibility: CanisterId ↦ CanisterSnapshotVisibility; + canister_logs: CanisterId ↦ [CanisterLog]; + query_stats: CanisterId ↦ [QueryStats]; + system_time : Timestamp + call_contexts : CallId ↦ CallCtxt; + messages : List Message; // ordered! + root_key : PublicKey +} +``` + +To convert `CanStatus` into `status : Running | Stopping | Stopped` from `Env`, we define the following conversion function: +``` +simple_status(Running) = Running +simple_status(Stopping _) = Stopping +simple_status(Stopped) = Stopped +``` + +To convert `CallOrigin` into `ChangeOrigin`, we define the following conversion function: +``` +change_origin(principal, _, FromUser { … }) = FromUser { + user_id = principal + } +change_origin(principal, sender_canister_version, FromCanister { … }) = FromCanister { + canister_id = principal + canister_version = sender_canister_version + } +change_origin(principal, sender_canister_version, FromSystemTask) = FromCanister { + canister_id = principal + canister_version = sender_canister_version + } +``` + +#### Cycle bookkeeping and resource consumption + +The main cycle balance of canister `A` in state `S` can be obtained with `S.balances(A)`. +In addition to the main balance, each canister has a reserved balance `S.reserved_balances(A)`. +The reserved balance contains cycles that were set aside from the main balance for future payments for the consumption of resources such as compute and memory. +The reserved cycles can only be used for resource payments and cannot be transferred back to the main balance. + +The (unspecified) function `idle_cycles_burned_rate(compute_allocation, memory_allocation, memory_usage, subnet_size)` determines the idle resource consumption rate in cycles per day of a canister given its current compute and memory allocation, memory usage, and subnet size. The function `freezing_limit(compute_allocation, memory_allocation, freezing_threshold, memory_usage, subnet_size)` determines the freezing limit in cycles of a canister given its current compute and memory allocation, freezing threshold in seconds, memory usage, and subnet size. The value `freezing_limit(compute_allocation, memory_allocation, freezing_threshold, memory_usage, subnet_size)` is derived from `idle_cycles_burned_rate(compute_allocation, memory_allocation, memory_usage, subnet_size)` and `freezing_threshold` as follows: +``` +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)`, `memory_usage_canister_history(canister_history)`, `memory_usage_chunk_store(chunk_store)`, and `memory_usage_snapshots(snapshots)` determine the canister's memory usage in bytes consumed by its Wasm state, raw Wasm binary, canister history, chunk store, and snapshots, 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_snapshots(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, 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 = (); + freezing_threshold = (); + canister_status = (); + canister_version = (); + canister_subnet = (); + time = (); + global_timer = (); + balances = (); + reserved_balances = (); + reserved_balance_limits = (); + wasm_memory_limit = (); + wasm_memory_threshold = (); + environment_variables = (); + on_low_wasm_memory_hook_status = (); + certified_data = (); + canister_history = (); + canister_log_visibility = (); + canister_snapshot_visibility = (); + canister_logs = (); + query_stats = (); + system_time = T; + call_contexts = (); + messages = []; + root_key = PublicKey; + subnet_admins = (); +} +``` + +for some time stamp `T`, some DER-encoded BLS public key `PublicKey`, and using `()` to denote the empty map or bag. + +### Invariants + +The following is an incomplete list of invariants that should hold for the abstract state `S`, and are not already covered by the type annotations in this section. + +- No pair of update, query, and composite query methods in a CanisterModule can have the same name: + ``` + ∀ (_ ↦ CanState) ∈ S.canisters: + dom(CanState.module.update_methods) ∩ dom(CanState.module.query_methods) = ∅ + dom(CanState.module.update_methods) ∩ dom(CanState.module.composite_query_methods) = ∅ + dom(CanState.module.query_methods) ∩ dom(CanState.module.composite_query_methods) = ∅ + ``` + +- Deleted call contexts were not awaiting a response: + ``` + ∀ (_ ↦ Ctxt) ∈ S.call_contexts: + if Ctxt.deleted then Ctxt.needs_to_respond = false + ``` +- Responded call contexts have no available\_cycles left: + ``` + ∀ (_ ↦ Ctxt) ∈ S.call_contexts: + if Ctxt.needs_to_respond = false then Ctxt.available_cycles = 0 + ``` +- A stopped canister does not have any call contexts (in particular, a stopped canister does not have any call contexts marked as deleted): + ``` + ∀ (_ ↦ Ctxt) ∈ S.call_contexts: + S.canister_status[Ctxt.canister] ≠ Stopped + ``` +- Referenced call contexts exist, unless the origins have expired deadlines: + + ``` + ∀ CallMessage {origin = FromCanister O, …} ∈ S.messages. O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) + ∀ ResponseMessage {origin = FromCanister O, …} ∈ S.messages. O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) + ∀ (_ ↦ {needs_to_respond = true, origin = FromCanister O, …}) ∈ S.call_contexts: O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) + ∀ (_ ↦ Stopping Origins) ∈ S.canister_status: ∀(FromCanister O, _) ∈ Origins. O.deadline ≠ Expired _ ⇒ O.calling_context ∈ dom(S.call_contexts) + ``` +### State transitions + +Based on this abstract notion of the state, we can describe the behavior of the IC. There are three classes of behaviors: + +- Potentially state changing API requests that are submitted via `/api/v2/…/call` and `/api/v4/…/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. + +- Responses to reads (i.e. `/api/v3/…/read_state` and `/api/v3/…/query`). By definition, these do *not* change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. + +The state transitions are not complete with regard to error handling. For example, the behavior of sending a request to a non-existent canister is not specified here. For now, we trust implementors to make sensible decisions there. + +We model the [The IC management canister](./management-canister.md#ic-management-canister) with one state transition per method. There, we assume a function +``` +candid : Value -> Blob +``` +that represents Candid encoding; this is implicitly taking the method types, as declared in [Interface overview](./management-canister.md#ic-candid), into account. We model the parsing of Candid values in the "Conditions" section using `candid` as well, by treating it as a non-deterministic function. + +#### Envelope Authentication + +The following predicate describes when an envelope `E` correctly signs the enclosed request with a key belonging to a user `U`, at time `T`: It returns which canister ids this envelope may be used at (as a set of principals). +``` +verify_envelope({ content = C }, U, T) + = { p : p is CanisterID } if U = anonymous_id +verify_envelope({ content = C, sender_pubkey = PK, sender_sig = Sig, sender_delegation = DS}, U, T) + = TS if U = mk_self_authenticating_id E.sender_pubkey + ∧ (PK', TS) = verify_delegations(DS, PK, T, { p : p is CanisterId }) + ∧ verify_signature PK' Sig ("\x0Aic-request" · hash_of_map(C)) +verify_delegations([], PK, T, TS) = (PK, TS) +verify_delegations([D] · DS, PK, T, TS) + = verify_delegations(DS, D.pubkey, T, TS ∩ delegation_targets(D)) + if verify_signature PK D.signature ("\x1Aic-request-auth-delegation" · hash_of_map(D.delegation)) + ∧ D.delegation.expiration ≥ T +delegation_targets(D) + = if D.targets = Unrestricted + then { p : p is CanisterId } + else D.targets +``` +#### Effective canister ids + +A `Request` has an effective canister id according to the rules in [Effective canister id](./https-interface.md#http-effective-canister-id): +``` +is_effective_canister_id(Request {canister_id = ic_principal, method = create_canister, …}, p) +is_effective_canister_id(Request {canister_id = ic_principal, method = provisional_create_canister_with_cycles, …}, p) +is_effective_canister_id(CanisterQuery {canister_id = ic_principal, method = list_canisters, …}, p) +is_effective_canister_id(Request {canister_id = ic_principal, method = install_chunked_code, arg = candid({target_canister = p, …}), …}, p) +is_effective_canister_id(Request {canister_id = ic_principal, arg = candid({canister_id = p, …}), …}, p) +is_effective_canister_id(Request {canister_id = p, …}, p), if p ≠ ic_principal +``` + +#### API Request submission {#api-request-submission} + +After a replica (i.e., a node belonging to an IC subnet) receives a call in an HTTP request to `/api/v2/canister//call` or `/api/v4/canister//call` +and if the replica accepts the call and subsequently the IC subnet (as a whole) receives the call, then the call gets added to the IC state as `Received`. + +This can only happen if the target canister is not frozen and + +- the target canister is not empty, the target canister is running, and ingress message inspection succeeds for calls to a regular canister; + +- the management canister method can be called via ingress messages and the caller is a controller of the target canister for calls to the management canister + (or the call targets the [IC Provisional API](./management-canister.md#ic-provisional-api) on a development instance). + +Moreover, the signature must be valid and created with a correct key. Due to this check, the envelope is discarded after this point. + +Finally, the system time (of the replica receiving the HTTP request) must not have exceeded the `ingress_expiry` field of the HTTP request containing the call. + +Submitted request to `/api//canister//call` + +```html + +E : Envelope + +``` + +where `` is `v2` or `v4`. + +Conditions + +```html + +E.content.canister_id ∈ verify_envelope(E, E.content.sender, S.system_time) +if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: + if not (E.content.sender_info = null): + verify_signature E.sender_pubkey E.content.sender_info.sig ("\x0Eic-sender-info" · E.content.sender_info.info) + E.content.sender_info.signer = Signing_canister_id +else: + E.content.sender_info = null +if E.content.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): + if E.content.sender_info = null: + Caller_info_data = "" + Caller_info_signer = "" + else: + Caller_info_data = E.content.sender_info.info + Caller_info_signer = Signing_canister_id +else: + Caller_info_data = "" + Caller_info_signer = "" +|E.content.nonce| <= 32 +E.content ∉ dom(S.requests) +S.system_time <= E.content.ingress_expiry +is_effective_canister_id(E.content, ECID) +liquid_balance(S, E.content.canister_id) ≥ 0 +( E.content.canister_id = ic_principal + E.content.arg = candid({canister_id = CanisterId, …}) + E.content.sender ∈ S.controllers[CanisterId] + E.content.method_name ∈ + { "install_code", "install_chunked_code", "update_settings", + "upload_chunk", "stored_chunks", "clear_chunk_store", + "take_canister_snapshot", "load_canister_snapshot", "list_canister_snapshots", "delete_canister_snapshot", + "read_canister_snapshot_metadata", "read_canister_snapshot_data", "upload_canister_snapshot_metadata", "upload_canister_snapshot_data", + "provisional_top_up_canister" } +) ∨ ( + E.content.canister_id = ic_principal + E.content.arg = candid({canister_id = CanisterId, …}) + E.content.sender ∈ S.controllers[CanisterId] ∪ S.subnet_admins[S.canister_subnet[CanisterId]] + E.content.method_name ∈ + { "start_canister", "stop_canister", "uninstall_code", "delete_canister", "canister_status" } +) ∨ ( + E.content.canister_id = ic_principal + E.content.sender ∈ S.subnet_admins[S.canister_subnet[ECID]] + E.content.method_name ∈ + { "create_canister" } +) ∨ ( + E.content.canister_id = ic_principal + E.content.method_name ∈ + { "provisional_create_canister_with_cycles" } +) ∨ ( + E.content.canister_id ≠ ic_principal + S.canisters[E.content.canister_id] ≠ EmptyCanister + S.canister_status[E.content.canister_id] = Running + Env = { + time = S.time[E.content.canister_id]; + controllers = S.controllers[E.content.canister_id]; + global_timer = S.global_timer[E.content.canister_id]; + balance = S.balances[E.content.canister_id]; + reserved_balance = S.reserved_balances[E.content.canister_id]; + reserved_balance_limit = S.reserved_balance_limits[E.content.canister_id]; + compute_allocation = S.compute_allocation[E.content.canister_id]; + 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_snapshots = memory_usage_snapshots(S.snapshots[E.content.canister_id]); + freezing_threshold = S.freezing_threshold[E.content.canister_id]; + subnet_id = S.canister_subnet[E.content.canister_id].subnet_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]; + } + 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, Caller_info_data, Caller_info_signer, Env) = Return {status = Accept;} +) + +``` + +State after + +```html + +S with + requests[E.content] = (Received, ECID) + +``` + +:::note + +This is not instantaneous (the IC takes some time to agree it accepts the request) nor guaranteed (a node could just drop the request, or maybe it did not pass validation). But once the request has entered the IC state like this, it will be acted upon. + +::: + +#### Request rejection + +The IC may reject a received message for internal reasons (high load, low resources) or expiry. The precise conditions are not specified here, but the reject code must indicate this to be a system error. + +Conditions + +```html + +S.requests[R] = (Received, ECID) +Code = SYS_FATAL or Code = SYS_TRANSIENT + +``` + +State after + +```html + +S with + requests[R] = (Rejected (Code, Msg), ECID) + +``` + +#### Initiating canister calls + +A first step in processing a canister update call is to create a `CallMessage` in the message queue. + +The `request` field of the `FromUser` origin establishes the connection to the API message. One could use the corresponding `hash_of_map` for this purpose, but this formulation is more abstract. + +The IC does not make any guarantees about the order of incoming messages. + +Conditions + +```html + +S.requests[R] = (Received, ECID) +S.system_time <= R.ingress_expiry +C = S.canisters[R.canister_id] + +if R.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): + if R.sender_info = null: + Caller_info_data = "" + Caller_info_signer = "" + else: + Caller_info_data = R.sender_info.info + Caller_info_signer = Signing_canister_id +else: + Caller_info_data = "" + Caller_info_signer = "" + +``` + +State after + +```html + +S with + requests[R] = (Processing, ECID) + messages = + CallMessage { + origin = FromUser { request = R }; + caller = R.sender; + caller_info_data = Caller_info_data; + caller_info_signer = Caller_info_signer; + callee = R.canister_id; + method_name = R.method_name; + arg = R.arg; + transferred_cycles = 0; + queue = Unordered; + } · S.messages + +``` + +#### Calls to stopped/stopping canisters are rejected + +A call to a canister which is stopping, or stopped is automatically rejected. + +Conditions + +```html + +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] = Stopped or S.canister_status[CM.callee] = Stopping +``` + +State after: + +```html + +messages = Older_messages · Younger_messages · + ResponseMessage { + origin = CM.origin; + response = Reject (CANISTER_ERROR, ); + refunded_cycles = CM.transferred_cycles; + } + +``` + +#### Calls to frozen canisters are rejected + +A call to a canister which is frozen is automatically rejected. + +Conditions + +```html + +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 +liquid_balance(S, CM.callee) < 0 +``` + +State after: + +```html + +messages = Older_messages · Younger_messages · + ResponseMessage { + origin = CM.origin; + response = Reject (SYS_TRANSIENT, ); + refunded_cycles = CM.transferred_cycles; + } + +``` + +#### Call context creation {#call-context-creation} + +Before invoking a heartbeat, a global timer, or a message to a public entry point, a call context is created for bookkeeping purposes. For these invocations the canister must be running (so not stopped or stopping). Additionally, these invocations only happen for "real" canisters, not the IC management canister. + +This "bookkeeping transition" must be immediately followed by the corresponding ["Message execution" transition](#rule-message-execution). + +*Call context creation: Public entry points* + +For a message to a public entry point, the method is looked up in the list of exports. This happens for both ingress and inter-canister messages. + +The position of the message in the queue is unchanged. + +Conditions + +```html + +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, CM.callee) ≥ MAX_CYCLES_PER_MESSAGE +Ctxt_id ∉ dom(S.call_contexts) + +``` + +State after + +```html + +S with + messages = + Older_messages · + FuncMessage { + call_context = Ctxt_id; + caller = CM.caller; + caller_info_data = CM.caller_info_data; + caller_info_signer = CM.caller_info_signer; + receiver = CM.callee; + entry_point = PublicMethod CM.method_name CM.caller CM.arg; + queue = CM.queue; + } · + Younger_messages + call_contexts[Ctxt_id] = { + canister = CM.callee; + origin = CM.origin; + needs_to_respond = true; + deleted = false; + available_cycles = CM.transferred_cycles; + } + balances[CM.callee] = S.balances[CM.callee] - MAX_CYCLES_PER_MESSAGE + +``` + +*Call context creation: Heartbeat* + +If canister `C` exports a method with name `canister_heartbeat`, the IC will create the corresponding call context. + +Conditions + +```html + +S.canisters[C] ≠ EmptyCanister +S.canister_status[C] = Running +liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE +Ctxt_id ∉ dom(S.call_contexts) + +``` + +State after + +```html + +S with + messages = + FuncMessage { + call_context = Ctxt_id; + caller = ic_principal; + caller_info_data = ""; + caller_info_signer = ""; + receiver = C; + entry_point = Heartbeat; + queue = Queue { from = System; to = C }; + } + · S.messages + call_contexts[Ctxt_id] = { + canister = C; + origin = FromSystemTask; + needs_to_respond = false; + deleted = false; + available_cycles = 0; + } + balances[C] = S.balances[C] - MAX_CYCLES_PER_MESSAGE + +``` + +*Call context creation: Global timer* + +If canister `C` exports a method with name `canister_global_timer`, the global timer of canister `C` is set, and the current time for canister `C` has passed the value of the global timer, the IC will create the corresponding call context and deactivate the global timer. + +Conditions + +```html + +S.canisters[C] ≠ EmptyCanister +S.canister_status[C] = Running +S.global_timer[C] ≠ 0 +S.time[C] ≥ S.global_timer[C] +liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE +Ctxt_id ∉ dom(S.call_contexts) + +``` + +State after + +```html + +S with + messages = + FuncMessage { + call_context = Ctxt_id; + caller = ic_principal; + caller_info_data = ""; + caller_info_signer = ""; + receiver = C; + entry_point = GlobalTimer; + queue = Queue { from = System; to = C }; + } + · S.messages + call_contexts[Ctxt_id] = { + canister = C; + origin = FromSystemTask; + needs_to_respond = false; + deleted = false; + available_cycles = 0; + } + global_timer[C] = 0 + balances[C] = S.balances[C] - MAX_CYCLES_PER_MESSAGE + +``` + +*Call context creation: On low wasm memory* + +If `S.on_low_wasm_memory_hook_status[C]` is `Ready` for a canister `C`, the IC will create the corresponding call context and set `S.on_low_wasm_memory_hook_status[C]` to `Executed`. + +Conditions + +```html + +S.canisters[C] ≠ EmptyCanister +S.canister_status[C] = Running +S.on_low_wasm_memory_hook_status[C] = Ready +liquid_balance(S, C) ≥ MAX_CYCLES_PER_MESSAGE +Ctxt_id ∉ dom(S.call_contexts) + +``` + +State after + +```html + +S with + messages = + FuncMessage { + call_context = Ctxt_id; + caller = ic_principal; + caller_info_data = ""; + caller_info_signer = ""; + receiver = C; + entry_point = OnLowWasmMemory; + queue = Queue { from = System; to = C }; + } + · S.messages + call_contexts[Ctxt_id] = { + canister = C; + origin = FromSystemTask; + needs_to_respond = false; + deleted = false; + available_cycles = 0; + } + on_low_wasm_memory_hook_status[C] = Executed + balances[C] = S.balances[C] - MAX_CYCLES_PER_MESSAGE + +``` + +The IC can execute any message that is at the head of its queue, i.e. there is no older message with the same abstract `queue` field. The actual message execution, if successful, may enqueue further messages and --- if the function returns a response --- record this response. The new call and response messages are enqueued at the end. + +Note that new messages are executed only if the canister is Running and is not frozen. + +#### Scheduling on low wasm memory hook {#rule-on-low-wasm-memory} + +This transition is executed immediately after [Message execution](#rule-message-execution) and IC Management Canister execution (update call). + +Conditions + +```html +if S.wasm_memory_limit[C] < |S.canisters[C].wasm_state.wasm_memory| + S.wasm_memory_threshold[C]: + if S.on_low_wasm_memory_hook_status[C] = ConditionNotSatisfied: + On_low_wasm_memory_hook_status = Ready + else: + On_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[C] +else: + On_low_wasm_memory_hook_status = ConditionNotSatisfied +``` + +State after + +```html +S with + on_low_wasm_memory_hook_status[C] = On_low_wasm_memory_hook_status +``` + +#### Message execution {#rule-message-execution} + +The transition models the actual execution of a message, whether it is an initial call to a public method or a response. In either case, a call context already exists (see transition "Call context creation"). + +For convenience, we first define a function that extracts the deadline from a call context. Note that user and system messages have no deadline. + +```html + +deadline_of_context(ctxt) = match ctxt.origin with + FromCanister O if O.deadline ≠ Expired _ → O.deadline + FromCanister O if O.deadline = Expired ts → ts + otherwise → NoDeadline + +``` + +Conditions + +```html + +S.messages = Older_messages · FuncMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +(∀ FuncMessage M' ∈ Older_messages · Younger_messages. M'.receiver ≠ M.receiver or M.entry_point ≠ OnLowWasmMemory) +S.on_low_wasm_memory_hook_status[M.receiver] ≠ Ready +S.canisters[M.receiver] ≠ EmptyCanister +Mod = S.canisters[M.receiver].module +Ctxt = S.call_contexts[M.call_context] +Deadline = deadline_of_context(Ctxt) + +Is_response = M.entry_point == Callback _ _ _ + +Env = { + time = S.time[M.receiver]; + controllers = S.controllers[M.receiver]; + global_timer = S.global_timer[M.receiver]; + balance = S.balances[M.receiver] + reserved_balance = S.reserved_balances[M.receiver]; + reserved_balance_limit = S.reserved_balance_limits[M.receiver]; + compute_allocation = S.compute_allocation[M.receiver]; + 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_snapshots = memory_usage_snapshots(S.snapshots[M.receiver]); + freezing_threshold = S.freezing_threshold[M.receiver]; + subnet_id = S.canister_subnet[M.receiver].subnet_id; + subnet_size = S.canister_subnet[M.receiver].subnet_size; + certificate = NoCertificate; + status = simple_status(S.canister_status[M.receiver]); + canister_version = S.canister_version[M.receiver]; +} + +Available = Ctxt.available_cycles +( M.entry_point = PublicMethod Name Caller Arg + F = Mod.update_methods[Name](Arg, M.caller, M.caller_info_data, M.caller_info_signer, Deadline, Env, Available) + New_canister_version = S.canister_version[M.receiver] + 1 + Wasm_memory_limit = S.wasm_memory_limit[M.receiver] +) +or +( M.entry_point = PublicMethod Name Caller Arg + F = query_as_update(Mod.query_methods[Name], Arg, M.caller, M.caller_info_data, M.caller_info_signer, Env, Available) + New_canister_version = S.canister_version[M.receiver] + Wasm_memory_limit = 0 +) +or +( M.entry_point = Callback Callback Response RefundedCycles + F = Mod.callbacks(Callback, M.caller, M.caller_info_data, M.caller_info_signer, Response, Deadline, RefundedCycles, Env, Available) + New_canister_version = S.canister_version[M.receiver] + 1 + Wasm_memory_limit = 0 +) +or +( M.entry_point = Heartbeat + F = system_task_as_update(Mod.heartbeat, Env) + New_canister_version = S.canister_version[M.receiver] + 1 + Wasm_memory_limit = 0 +) +or +( M.entry_point = GlobalTimer + F = system_task_as_update(Mod.global_timer, Env) + New_canister_version = S.canister_version[M.receiver] + 1 + Wasm_memory_limit = 0 +) +or +( M.entry_point = OnLowWasmMemory + F = system_task_as_update(Mod.on_low_wasm_memory, Env) + New_canister_version = S.canister_version[M.receiver] + 1 + Wasm_memory_limit = 0 +) + +R = F(S.canisters[M.receiver].wasm_state) + +``` + +State after + +```html + +if + R = Return res + validate_sender_canister_version(res.new_calls, S.canister_version[M.receiver]) + res.cycles_used ≤ (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE) + 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], 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 ]) + - Cycles_reserved + New_reserved_balance = S.reserved_balances[M.receiver] + Cycles_reserved + Min_balance = if Is_response then 0 else freezing_limit( + S.compute_allocation[M.receiver], + S.memory_allocation[M.receiver], + 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_chunk_store(S.chunk_store[M.receiver]) + + memory_usage_snapshots(S.snapshots[M.receiver]), + S.canister_subnet[M.receiver].subnet_size, + ) + New_reserved_balance ≤ S.reserved_balance_limits[M.receiver] + liquid_balance( + New_balance, + New_reserved_balance, + Min_balance + ) ≥ 0 + (Wasm_memory_limit = 0) or |res.new_state.wasm_memory| <= Wasm_memory_limit + (res.response = NoResponse) or Ctxt.needs_to_respond +then + S with + canisters[M.receiver].wasm_state = res.new_state; + canister_version[M.receiver] = New_canister_version; + messages = + Older_messages · + Younger_messages · + [ CallMessage { + origin = FromCanister { + call_context = M.call_context; + callback = call.callback; + deadline = if call.timeout_seconds ≠ NoTimeout + then S.time[M.receiver] + call.timeout_seconds * 10^9 + else NoDeadline + + }; + caller = M.receiver; + caller_info_data = ""; + caller_info_signer = ""; + callee = call.callee; + method_name = call.method_name; + arg = call.arg; + transferred_cycles = call.transferred_cycles + queue = Queue { from = M.receiver; to = call.callee }; + } + | call ∈ res.new_calls ] · + [ ResponseMessage { + origin = Ctxt.origin; + response = res.response; + refunded_cycles = Available - res.cycles_accepted; + } + | res.response ≠ NoResponse ] + + if res.response = NoResponse: + call_contexts[M.call_context].available_cycles = Available - res.cycles_accepted + else + call_contexts[M.call_context].needs_to_respond = false + call_contexts[M.call_context].available_cycles = 0 + + if res.new_certified_data ≠ NoCertifiedData: + certified_data[M.receiver] = res.new_certified_data + + if res.new_global_timer ≠ NoGlobalTimer: + global_timer[M.receiver] = res.new_global_timer + + balances[M.receiver] = New_balance + reserved_balances[M.receiver] = New_reserved_balance + + canister_logs[M.receiver] = S.canister_logs[M.receiver] · canister_logs +else + S with + messages = Older_messages · Younger_messages + balances[M.receiver] = + (S.balances[M.receiver] + (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE)) + - min (R.cycles_used, (if Is_response then MAX_CYCLES_PER_RESPONSE else MAX_CYCLES_PER_MESSAGE)) + +``` + +Depending on whether this is a call message and a response messages, we have either set aside `MAX_CYCLES_PER_MESSAGE` or `MAX_CYCLES_PER_RESPONSE`, either in the call context creation rule or the Callback invocation rule. + +The cycle consumption of executing this message is modeled via the unspecified `cycles_used` variable; the variable takes some value between 0 and `MAX_CYCLES_PER_MESSAGE`/`MAX_CYCLES_PER_RESPONSE` (for call execution and response execution, respectively). + +The logs produced by the canister during message execution are modeled via the unspecified `canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[M.receiver]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps. + +This transition detects certain behavior that will appear as a trap (and which an implementation may implement by trapping directly in a system call): + +- Responding if the present call context does not need to be responded to + +- Accepting more cycles than are available on the call context + +- Sending out more cycles than available to the canister + +- Consuming more cycles than allowed (and reserved) + +If message execution [*traps* (in the sense of a Wasm function)](./canister-interface.md#system-api-module), the message gets dropped. No response is generated (as some other message may still fulfill this calling context). Any state mutation is discarded. If the message was a call, the associated cycles are held by its associated call context and will be refunded to the caller, see [Call context starvation](#rule-starvation). + +If message execution [*returns* (in the sense of a Wasm function)](./canister-interface.md#system-api-module), the state is updated and possible outbound calls and responses are enqueued. + +Note that returning does *not* imply that the call associated with this message now *succeeds* in the sense defined in [section responding](./canister-interface.md#responding); that would require a (unique) call to `ic0.reply`. Note also that the state changes are persisted even when the IC is set to synthesize a [CANISTER\_ERROR](./https-interface.md#reject-codes) reject immediately afterward (which happens when this returns without calling `ic0.reply` or `ic0.reject`, the corresponding call has not been responded to and there are no outstanding callbacks, see [Call context starvation](#rule-starvation)). + +The function `validate_sender_canister_version` checks that `sender_canister_version` matches the actual canister version of the sender in all calls to the methods of the management canister that take `sender_canister_version`: +``` +validate_sender_canister_version(new_calls, canister_version_from_system) = + ∀ call ∈ new_calls. (call.callee = ic_principal and (call.method = 'create_canister' or call.method = 'update_settings' or call.method = 'install_code' or call.method = `install_chunked_code` or call.method = 'uninstall_code' or call.method = 'provisional_create_canister_with_cycles') and call.arg = candid(A) and A.sender_canister_version = n) => n = canister_version_from_system +``` + +The functions `query_as_update` and `system_task_as_update` turns a query function (note that composite query methods cannot be called when executing a message during this transition) resp the heartbeat or global timer into an update function; this is merely a notational trick to simplify the rule: +``` +query_as_update(f, arg, caller, caller_info_data, caller_info_signer, env, available) = λ wasm_state → + match f(arg, caller, caller_info_data, caller_info_signer, env, available)(wasm_state) with + Trap trap → Trap trap + Return res → Return { + new_state = wasm_state; + new_calls = []; + new_certified_data = NoCertifiedData; + new_global_timer = NoGlobalTimer; + response = res.response; + cycles_accepted = res.cycles_accepted; + cycles_used = res.cycles_used; + } + +system_task_as_update(f, env) = λ wasm_state → + match f(env)(wasm_state) with + Trap trap → Trap trap + Return res → Return { + new_state = res.new_state; + new_calls = res.new_calls; + new_certified_data = res.new_certified_data; + new_global_timer = res.new_global_timer; + response = NoResponse; + cycles_accepted = 0; + cycles_used = res.cycles_used; + } +``` + +Note that by construction, a query function will either trap or return with a response; it will never send calls, and it will never change the state of the canister. + +#### Spontaneous request rejection {#request-rejection} + +The system can reject a request at any point in time, e.g., because it is overloaded. + +Condition: +```html +S.messages = Older_messages · CallMessage CM · Younger_messages +``` + +State after, with `reject_code` being an arbitrary reject code: +```html +S.messages = + Older_messages + · Younger_messages + · ResponseMessage { + origin = CM.origin; + response = Reject (reject_code, ); + refunded_cycles = CM.transferred_cycles; + } +``` + +#### Call expiry {#call-expiry} + +These transitions expire bounded-wait calls. The transition can be taken before the specified call deadline (e.g., due to high system load), and we thus ignore the caller time in these transitions. We define two variants of the transition, one that expires messages, and one that expires calls that are in progress (i.e., have open downstream call contexts). + +The first transition defines the expiry of messages. + +Condition: + +```html +S.messages = Older_messages · M · Younger_messages +M = CallMessage _ ∨ M = ResponseMessage _ +M.origin = FromCanister O +O.deadline = timestamp +``` + +State after: + +```html +S.messages = Older_messages · (M with origin = FromCanister O with deadline = Expired timestamp) · Younger_messages · + ResponseMessage { + origin = FromCanister O with deadline = NoDeadline; + response = Reject (SYS_UNKNOWN, ); + refunded_cycles = 0; + } +``` + +The next two transitions define the expiry of calls that are being processed by the callee. The first transition deals with regular calls. + +Condition: + +```html +ctxt_id ∈ S.call_contexts +S.call_contexts[ctxt_id].origin = FromCanister O +S.call_contexts[ctxt_id].needs_to_respond = true +O.deadline = timestamp +``` + +State after: + +```html +S.call_contexts[ctxt_id].origin = FromCanister O with deadline = Expired timestamp +S.messages = S.messages · ResponseMessage { + origin = FromCanister O with deadline = NoDeadline; + response = Reject (SYS_UNKNOWN, ); + refunded_cycles = 0; +} +``` + +The second transition deals with the special case of a call that's trying to stop the `target_canister` + +Condition: + +```html +S.canister_status[target_canister] = Stopping (prefix · (FromCanister O, stop_ts) · suffix) +O.deadline = timestamp +``` + +State after: + +```html +S.canister_status[target_canister] = + Stopping (prefix · (FromCanister O with deadline = Expired timestamp, stop_ts) · suffix) +S.messages = S.messages · ResponseMessage { + origin = FromCanister O with deadline = NoDeadline; + response = Reject (SYS_UNKNOWN, ); + refunded_cycles = 0; +} +``` + +#### Call context starvation {#rule-starvation} + +If the call context needs to respond (in particular, if the call context is not for a system task) and there is no call, downstream call context, or response that references a call context, then a reject is synthesized. The error message below is *not* indicative. In particular, if the IC has an idea about *why* this starved, it can put that in there (e.g. the initial message handler trapped with an out-of-memory access). + +Conditions + +```html + +S.call_contexts[Ctxt_id].needs_to_respond = true +∀ CallMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ +∀ ResponseMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ +∀ (_ ↦ {needs_to_respond = true, origin = FromCanister O, …}) ∈ S.call_contexts: O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ +∀ (_ ↦ Stopping Origins) ∈ S.canister_status: ∀(FromCanister O, _) ∈ Origins. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ + +``` + +State after + +```html + +S with + call_contexts[Ctxt_id].needs_to_respond = false + call_contexts[Ctxt_id].available_cycles = 0 + messages = + S.messages · + ResponseMessage { + origin = S.call_contexts[Ctxt_id].origin; + response = Reject (CANISTER_ERROR, ); + refunded_cycles = S.call_contexts[Ctxt_id].available_cycles; + } + +``` + +#### Call context removal {#call-context-removal} + +If there is no call, downstream call context, or response that references a call context, and the call context does not need to respond (because it has already responded or its origin is a system task that does not await a response), then the call context can be removed. + +Conditions + +```html + +S.call_contexts[Ctxt_id].needs_to_respond = false +∀ CallMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ +∀ ResponseMessage {origin = FromCanister O, …} ∈ S.messages. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ +∀ (_ ↦ {needs_to_respond = true, origin = FromCanister O, …}) ∈ S.call_contexts: O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ +∀ (_ ↦ Stopping Origins) ∈ S.canister_status: ∀(FromCanister O, _) ∈ Origins. O.calling_context ≠ Ctxt_id ∨ O.deadline = Expired _ + +``` + +State after + +```html + +S with + call_contexts[Ctxt_id] = (deleted) + +``` + +#### IC Management Canister: Canister creation + +The IC chooses an appropriate canister id (referred to as `CanisterId`) and subnet id (referred to as `SubnetId`, `SubnetId ∈ Subnets`, where `Subnets` is the under-specified set of subnet ids on the IC) and instantiates a new (empty) canister identified by `CanisterId` on the subnet identified by `SubnetId` with subnet size denoted by `SubnetSize`. The *controllers* are set such that the sender of this request is the only controller, unless the `settings` say otherwise. All cycles on this call are now the canister's initial cycles. + +This is also when the System Time of the new canister starts ticking. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'create_canister' +M.arg = candid(A) +is_system_assigned Canister_id +Canister_id ∉ dom(S.canisters) +SubnetId ∈ Subnets +(A.settings.environment_variables = null or + (|A.settings.environment_variables| ≤ MAX_ENV_VAR_COUNT and + ∀(name, value) ∈ A.settings.environment_variables: + |name| ≤ MAX_ENV_VAR_NAME_LENGTH and + |value| ≤ MAX_ENV_VAR_VALUE_LENGTH and + is_valid_utf8(name) and + is_valid_utf8(value))) + +if A.settings.controllers is not null: + New_controllers = A.settings.controllers +else: + New_controllers = [M.caller] + +if A.settings.compute_allocation is not null: + New_compute_allocation = A.settings.compute_allocation +else: + New_compute_allocation = 0 +if A.settings.memory_allocation is not null: + New_memory_allocation = A.settings.memory_allocation +else: + New_memory_allocation = 0 +if A.settings.freezing_threshold is not null: + New_freezing_threshold = A.settings.freezing_threshold +else: + New_freezing_threshold = 2592000 +if A.settings.reserved_cycles_limit is not null: + New_reserved_balance_limit = A.settings.reserved_cycles_limit +else: + New_reserved_balance_limit = 5_000_000_000_000 +if A.settings.wasm_memory_limit is not null: + New_wasm_memory_limit = A.settings.wasm_memory_limit +else: + New_wasm_memory_limit = 0 +if A.settings.wasm_memory_threshold is not null: + New_wasm_memory_threshold = A.settings.wasm_memory_threshold +else: + New_wasm_memory_threshold = 0 +if A.settings.environment_variables is not null: + New_environment_variables = A.settings.environment_variables +else: + New_environment_variables = [] + +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(S', Canister_id) ≥ 0 + +New_canister_history = { + total_num_changes = 1 + recent_changes = { + timestamp_nanos = CurrentTime + canister_version = 0 + origin = change_origin(M.caller, A.sender_canister_version, M.origin) + details = Creation { + controllers = New_controllers + environment_variables_hash = if A.settings.environment_variables is not null then + opt hash_of_map(A.settings.environment_variables) + else + null + } + } +} + +if A.settings.log_visibility is not null: + New_canister_log_visibility = A.settings.log_visibility +else: + New_canister_log_visibility = Controllers + +if A.settings.snapshot_visibility is not null: + New_canister_snapshot_visibility = A.settings.snapshot_visibility +else: + New_canister_snapshot_visibility = Controllers +``` + +State after + +```html + +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 + chunk_store[Canister_id] = () + compute_allocation[Canister_id] = New_compute_allocation + memory_allocation[Canister_id] = New_memory_allocation + freezing_threshold[Canister_id] = New_freezing_threshold + balances[Canister_id] = New_balance + reserved_balances[Canister_id] = New_reserved_balance + reserved_balance_limits[Canister_id] = New_reserved_balance_limit + wasm_memory_limit[Canister_id] = New_wasm_memory_limit + wasm_memory_threshold[Canister_id] = New_wasm_memory_threshold + environment_variables[Canister_id] = New_environment_variables + on_low_wasm_memory_hook_status[Canister_id] = ConditionNotSatisfied + certified_data[Canister_id] = "" + query_stats[Canister_id] = [] + canister_history[Canister_id] = New_canister_history + canister_log_visibility[Canister_id] = New_canister_log_visibility + canister_snapshot_visibility[Canister_id] = New_canister_snapshot_visibility + canister_logs[Canister_id] = [] + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid({canister_id = Canister_id})) + refunded_cycles = 0 + } + canister_status[Canister_id] = Running + canister_version[Canister_id] = 0 + canister_subnet[Canister_id] = Subnet { + subnet_id : SubnetId + subnet_size : SubnetSize + } + +``` + +This uses the predicate +``` +is_system_assigned : Principal -> Bool +``` +which characterizes all system-assigned ids. + +To avoid clashes with potential user ids or is derived from users or canisters, we require (somewhat handwavy) that + +- `is_system_assigned (mk_self_authenticating_id pk) = false` for possible public keys `pk` and + +- `is_system_assigned (mk_derived_id p dn) = false` for any `p` that could be a user id or canister id. + +- `is_system_assigned p = false` for `|p| > 29`. + +- `is_system_assigned ic_principal = false`. + +#### IC Management Canister: Changing settings + +Only the controllers of the given canister can update the canister settings. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'update_settings' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] +(A.settings.environment_variables = null or + (|A.settings.environment_variables| ≤ MAX_ENV_VAR_COUNT and + ∀(name, value) ∈ A.settings.environment_variables: + |name| ≤ MAX_ENV_VAR_NAME_LENGTH and + |value| ≤ MAX_ENV_VAR_VALUE_LENGTH and + is_valid_utf8(name) and + is_valid_utf8(value))) + +if New_wasm_memory_limit > 0: + |S.canisters[A.canister_id].wasm_state.wasm_memory| ≤ New_wasm_memory_limit + +if A.settings.compute_allocation is not null: + New_compute_allocation = A.settings.compute_allocation +else: + New_compute_allocation = S.compute_allocation[A.canister_id] +if A.settings.memory_allocation is not null: + New_memory_allocation = A.settings.memory_allocation +else: + New_memory_allocation = S.memory_allocation[A.canister_id] +if A.settings.freezing_threshold is not null: + New_freezing_threshold = A.settings.freezing_threshold +else: + New_freezing_threshold = S.freezing_threshold[A.canister_id] +if A.settings.reserved_cycles_limit is not null: + New_reserved_balance_limit = A.settings.reserved_cycles_limit +else: + New_reserved_balance_limit = S.reserved_balance_limits[A.canister_id] +if A.settings.wasm_memory_limit is not null: + New_wasm_memory_limit = A.settings.wasm_memory_limit +else: + New_wasm_memory_limit = S.wasm_memory_limit[A.canister_id] +if A.settings.wasm_memory_threshold is not null: + New_wasm_memory_threshold = A.settings.wasm_memory_threshold +else: + New_wasm_memory_threshold = S.wasm_memory_threshold[A.canister_id] +if A.settings.environment_variables is not null: + New_environment_variables = A.settings.environment_variables +else: + New_environment_variables = S.environment_variables[A.canister_id] + +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(S', A.canister_id) ≥ 0 + +S.canister_history[A.canister_id] = { + total_num_changes = N; + recent_changes = H; +} + +if A.settings.controllers is not null: + 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 = ControllersChange { + controllers = A.settings.controllers; + }; + }; + } +else: + New_canister_history = S.canister_history[A.canister_id] + +``` + +State after + +```html + +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 + compute_allocation[A.canister_id] = New_compute_allocation + memory_allocation[A.canister_id] = New_memory_allocation + freezing_threshold[A.canister_id] = New_freezing_threshold + balances[A.canister_id] = New_balance + reserved_balances[A.canister_id] = New_reserved_balance + reserved_balance_limits[A.canister_id] = New_reserved_balance_limit + wasm_memory_limit[A.canister_id] = New_wasm_memory_limit + wasm_memory_threshold[A.canister_id] = New_wasm_memory_threshold + environment_variables[A.canister_id] = New_environment_variables + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + if A.settings.log_visibility is not null: + canister_log_visibility[A.canister_id] = A.settings.log_visibility + if A.settings.snapshot_visibility is not null: + canister_snapshot_visibility[A.canister_id] = A.settings.snapshot_visibility + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid()) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Canister status + +The controllers of a canister can obtain detailed information about the canister. + +Given a state `S` and `Canister_id`, we define + +```html + +canister_status(S, Canister_id) = + { + status = simple_status(S.canister_status[Canister_id]); + settings = { + controllers = S.controllers[Canister_id]; + compute_allocation = S.compute_allocation[Canister_id]; + memory_allocation = S.memory_allocation[Canister_id]; + freezing_threshold = S.freezing_threshold[Canister_id]; + reserved_cycles_limit = S.reserved_balance_limit[Canister_id]; + wasm_memory_limit = S.wasm_memory_limit[Canister_id]; + wasm_memory_threshold = S.wasm_memory_threshold[Canister_id]; + environment_variables = S.environment_variables[Canister_id]; + } + module_hash = + if S.canisters[Canister_id] = EmptyCanister + then null + else opt (SHA-256(S.canisters[Canister_id].raw_module)); + memory_size = Memory_usage; + memory_metrics = Memory_metrics; + cycles = S.balances[Canister_id]; + reserved_cycles = S.reserved_balances[Canister_id] + idle_cycles_burned_per_day = idle_cycles_burned_rate( + S.compute_allocation[Canister_id], + S.memory_allocation[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]) + + memory_usage_chunk_store(S.chunk_store[Canister_id]) + + memory_usage_snapshots(S.snapshots[Canister_id]), + S.freezing_threshold[Canister_id], + S.canister_subnet[Canister_id].subnet_size, + ); + query_stats = noise(SUM {{num_calls_total: 1, + num_instructions_total: single_query_stats.num_instructions, + request_payload_bytes_total: single_query_stats.request_payload_bytes, + response_payload_bytes_total: single_query_stats.response_payload_bytes} | + single_query_stats <- S.query_stats[Canister_id]; + single_query_stats.timestamp <= S.time[Canister_id] - T}) + } + +``` + +where +- `Memory_usage` is the (in this specification underspecified) total canister memory usage in bytes; +- `Memory_metrics` are the (in this specification underspecified) detailed metrics on the memory consumption of the canister (see [Memory Metrics](./management-canister.md#ic-canister_status-memory_metrics) for more details); +- `T` is an (in this specification underspecified) time delay of query statistics and `noise` is an (in this specification underspecified) probabilistic function +modelling information loss due to aggregating query statistics in a distributed system. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'canister_status' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = candid(canister_status(S, A.canister_id)) + refunded_cycles = M.transferred_cycles + } + +``` + +The IC method `canister_status` can also be invoked via management canister query calls. +They are calls to `/api/v3/canister//query` +with CBOR content `Q` such that `Q.canister_id = ic_principal`. + +Submitted request to `/api/v3/canister//query` + +```html + +E : Envelope + +``` + +Conditions + +```html + +E.content = CanisterQuery Q +Q.canister_id = ic_principal +Q.method_name = 'canister_status' +|Q.nonce| <= 32 +is_effective_canister_id(E.content, ECID) +S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id +Q.arg = candid(A) +A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) +if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: + if not (Q.sender_info = null): + verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) + Q.sender_info.signer = Signing_canister_id +else: + Q.sender_info = null +Q.sender ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +Query response `R`: + +```html + +{status: "replied"; reply: {arg: candid(canister_status(S, A.canister_id))}, signatures: Sigs} + +``` + +where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: + +```html + +verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" + +``` + +#### IC Management Canister: Canister information + +Every canister can retrieve the canister history, current module hash, and current controllers of every other canister (including itself). + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'canister_info' +M.arg = candid(A) +if A.num_requested_changes = null then From = |S.canister_history[A.canister_id].recent_changes| +else From = max(0, |S.canister_history[A.canister_id].recent_changes| - A.num_requested_changes) +End = |S.canister_history[A.canister_id].recent_changes| - 1 + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = candid({ + total_num_changes = S.canister_history[A.canister_id].total_num_changes; + recent_changes = S.canister_history[A.canister_id].recent_changes[From..End]; + module_hash = + if S.canisters[A.canister_id] = EmptyCanister + then null + else opt (SHA-256(S.canisters[A.canister_id].raw_module)); + controllers = S.controllers[A.canister_id]; + }) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Canister metadata + +Every canister can retrieve public metadata of every other canister (including itself) +and private metadata of canisters controlled by the caller. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'canister_metadata' +M.arg = candid(A) +A.name ∈ dom(S.canisters[A.canister_id].public_custom_sections) ∨ (A.name ∈ dom(S.canisters[A.canister_id].private_custom_sections) ∧ M.caller ∈ S.controllers[A.canister_id]) +if A.name ∈ dom(S.canisters[A.canister_id].public_custom_sections): + Content = S.canisters[A.canister_id].public_custom_sections[A.name] +else: + Content = S.canisters[A.canister_id].private_custom_sections[A.name] + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = candid({ + value = Content; + }) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Upload Chunk + +A controller of a canister, or the canister itself can upload chunks to the chunk store of that canister. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'upload_chunk' +M.arg = candid(A) +|dom(S.chunk_store[A.canister_id]) ∪ {SHA-256(A.chunk)}| <= CHUNK_STORE_SIZE +M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} + + +``` + +State after + +```html + +S with + chunk_store[A.canister_id](SHA-256(A.chunk)) = A.chunk + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = candid({hash: hash}) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Clear chunk store + +The controller of a canister, or the canister itself can clear the chunk store of that canister. + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'clear_chunk_store' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} +``` + +State after + +```html + +S with + chunk_store[A.canister_id] = () + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = candid() + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: List stored chunks + +The controller of a canister, or the canister itself can list the hashes of the chunks stored in the chunk store. + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'stored_chunks' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] ∪ {A.canister_id} + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = candid([{hash: hash} | hash <- dom(S.chunk_store[A.canister_id])]) + refunded_cycles = M.transferred_cycles + } + +``` + + + + +#### IC Management Canister: Code installation + +Only the controllers of the given canister can install code. This transition installs new code over a canister. This involves invoking the `canister_init` method (see [Canister initialization](./canister-interface.md#system-api-init)), which must succeed. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'install_code' +M.arg = candid(A) +Mod = parse_wasm_mod(A.wasm_module) +Public_custom_sections = parse_public_custom_sections(A.wasm_module); +Private_custom_sections = parse_private_custom_sections(A.wasm_module); +(A.mode = install and S.canisters[A.canister_id] = EmptyCanister) or A.mode = reinstall +M.caller ∈ S.controllers[A.canister_id] + +dom(Mod.update_methods) ∩ dom(Mod.query_methods) = ∅ +dom(Mod.update_methods) ∩ dom(Mod.composite_query_methods) = ∅ +dom(Mod.query_methods) ∩ dom(Mod.composite_query_methods) = ∅ + +Env = { + time = S.time[A.canister_id]; + controllers = S.controllers[A.canister_id]; + global_timer = 0; + balance = S.balances[A.canister_id]; + reserved_balance = S.reserved_balances[A.canister_id]; + reserved_balance_limit = S.reserved_balance_limits[A.canister_id]; + compute_allocation = S.compute_allocation[A.canister_id]; + 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_snapshots = memory_usage_snapshots(S.snapshots[A.canister_id]); + freezing_threshold = S.freezing_threshold[A.canister_id]; + subnet_id = S.canister_subnet[A.canister_id].subnet_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] + 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], 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, A.canister_id) ≥ MAX_CYCLES_PER_MESSAGE + +liquid_balance(S', A.canister_id) ≥ 0 + +(S.wasm_memory_limit[A.canister_id] = 0) or |New_state.wasm_memory| <= S.wasm_memory_limit[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 = CodeDeployment { + mode = A.mode; + module_hash = SHA-256(A.wasm_module); + }; + }; +} + +``` + +State after + +```html + +S' = S with + canisters[A.canister_id] = { + wasm_state = New_state; + module = Mod; + raw_module = A.wasm_module; + public_custom_sections = Public_custom_sections; + private_custom_sections = Private_custom_sections; + } + certified_data[A.canister_id] = New_certified_data + if New_global_timer ≠ NoGlobalTimer: + global_timer[A.canister_id] = New_global_timer + else: + global_timer[A.canister_id] = 0 + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + balances[A.canister_id] = New_balance + reserved_balances[A.canister_id] = New_reserved_balance + canister_history[A.canister_id] = New_canister_history + canister_logs[A.canister_id] = New_canister_logs + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin; + response = Reply (candid()); + refunded_cycles = M.transferred_cycles; + } + +``` + +The logs produced by the canister during the execution of the WebAssembly `start` and `canister_init` functions are modeled via the unspecified `New_canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[A.canister_id]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps. + +#### IC Management Canister: Code upgrade + +Only the controllers of the given canister can install new code. This changes the code of an *existing* canister, preserving the state in the stable memory. This involves invoking the `canister_pre_upgrade` method, if the `skip_pre_upgrade` flag is not set to `opt true`, on the old and `canister_post_upgrade` method on the new canister, which must succeed and must not invoke other methods. If the `wasm_memory_persistence` flag is set to `opt keep`, then the WebAssembly memory is preserved. + +If the old canister module exports a private custom section with the name "enhanced-orthogonal-persistence", then the `wasm_memory_persistence` option must be set to `opt keep` or `opt replace`, i.e., the option must not be `null`. + +If the `wasm_memory_persistence` option is set to `opt keep`, then the new canister module must export a private custom section with the name "enhanced-orthogonal-persistence". + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'install_code' +M.arg = candid(A) +Mod = parse_wasm_mod(A.wasm_module) +Public_custom_sections = parse_public_custom_sections(A.wasm_module) +Private_custom_sections = parse_private_custom_sections(A.wasm_module) +M.caller ∈ S.controllers[A.canister_id] +S.canisters[A.canister_id] = { wasm_state = Old_state; module = Old_module, …} + +dom(Mod.update_methods) ∩ dom(Mod.query_methods) = ∅ +dom(Mod.update_methods) ∩ dom(Mod.composite_query_methods) = ∅ +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]; + compute_allocation = S.compute_allocation[A.canister_id]; + 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_snapshots = memory_usage_snapshots(S.snapshots[A.canister_id]); + freezing_threshold = S.freezing_threshold[A.canister_id]; + subnet_id = S.canister_subnet[A.canister_id].subnet_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]; +} + +( + (A.mode = upgrade U and U.skip_pre_upgrade ≠ true) + Env1 = Env with { + global_timer = S.global_timer[A.canister_id]; + canister_version = S.canister_version[A.canister_id]; + } + Old_module.pre_upgrade(Old_State, M.caller, Env1) = Return {new_state = Intermediate_state; new_certified_data = New_certified_data; cycles_used = Cycles_used;} +) +or +( + (A.mode = upgrade U and U.skip_pre_upgrade = true) + Intermediate_state = Old_state + New_certified_data = NoCertifiedData + Cycles_used = 0 +) + +( + (A.mode = upgrade U and U.wasm_memory_persistence ≠ keep) + Persisted_state = { + wasm_memory = ""; + stable_memory = Intermediate_state.stable_memory; + globals = Mod.initial_globals; + self_id = A.canister_id; + } +) +or +( + (A.mode = upgrade U and U.wasm_memory_persistence = keep) + Persisted_state = { + wasm_memory = Intermediate_state.wasm_memory; + stable_memory = Intermediate_state.stable_memory; + globals = Mod.initial_globals; + self_id = A.canister_id; + } +) + +(A.mode = upgrade U and U.wasm_memory_persistence = keep) +or +(A.mode = upgrade U and U.wasm_memory_persistence = replace) +or +(S.canisters[A.canister_id].private_custom_sections["enhanced-orthogonal-persistence"] = null) + +not (A.mode = upgrade U and U.wasm_memory_persistence = keep and Private_custom_sections["enhanced-orthogonal-persistence"] = null) + +Env2 = Env with { + memory_usage_raw_module = memory_usage_raw_module(A.wasm_module); + memory_usage_canister_history = memory_usage_canister_history(New_canister_history); + global_timer = 0; + canister_version = S.canister_version[A.canister_id] + 1; +} + +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], 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, A.canister_id) ≥ MAX_CYCLES_PER_MESSAGE + +liquid_balance(S', A.canister_id) ≥ 0 + +(S.wasm_memory_limit[A.canister_id] = 0) or |New_state.wasm_memory| <= S.wasm_memory_limit[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 = CodeDeployment { + mode = Upgrade; + module_hash = SHA-256(A.wasm_module); + }; + }; +} +``` + +State after + +```html + +S' = S with + canisters[A.canister_id] = { + wasm_state = New_state; + module = Mod; + raw_module = A.wasm_module; + public_custom_sections = Public_custom_sections; + private_custom_sections = Private_custom_sections; + } + if New_certified_data' ≠ NoCertifiedData: + certified_data[A.canister_id] = New_certified_data' + else if New_certified_data ≠ NoCertifiedData: + certified_data[A.canister_id] = New_certified_data + if New_global_timer ≠ NoGlobalTimer: + global_timer[A.canister_id] = New_global_timer + else: + global_timer[A.canister_id] = 0 + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + balances[A.canister_id] = New_balance; + reserved_balances[A.canister_id] = New_reserved_balance; + canister_history[A.canister_id] = New_canister_history + canister_logs[A.canister_id] = S.canister_logs[A.canister_id] · canister_logs + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin; + response = Reply (candid()); + refunded_cycles = M.transferred_cycles; + } + +``` + +The logs produced by the canister during the execution of the WebAssembly `canister_pre_upgrade`, `start`, and `canister_post_upgrade` functions are modeled via the unspecified `canister_logs` variable; the variable stores a list of logs (each of type `CanisterLog`) with consecutive sequence numbers, timestamps equal to `S.time[A.canister_id]`, and contents produced by the canister calling `ic0.debug_print`, `ic0.trap`, or produced by the WebAssembly runtime when the canister WebAssembly module traps. + +#### IC Management Canister: Install chunked code + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'install_chunked_code' +M.arg = candid(A) +if A.store_canister = null then + store_canister = A.target_canister +else + store_canister = A.store_canister +M.caller ∈ S.controllers[A.target_canister] +M.caller ∈ S.controllers[store_canister] ∪ {store_canister} +S.canister_subnet[A.target_canister] = S.canister_subnet[strorage_canister] +∀ h ∈ A.chunk_hashes_list. h ∈ dom(S.chunk_store[store_canister]) +A.chunk_hashes_list = [h1,h2,...,hk] +wasm_module = S.chunk_store[store_canister][h1] || ... || S.chunk_store[store_canister][hk] +A.wasm_module_hash = SHA-256(wasm_module) +M' = M with + method_name = 'install_code' + arg = candid(record {A.mode; A.target_canister; wasm_module; A.arg; A.sender_canister_version}) + +``` + +State after + +```html + +S with + messages = Older_messages · CallMessage M' · Younger_messages + +``` + +#### IC Management Canister: Code uninstallation {#rule-uninstall} + +Upon uninstallation, the canister is reverted to an empty canister, and all outstanding call contexts are rejected and marked as deleted. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'uninstall_code' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] +S.canister_history[A.canister_id] = { + total_num_changes = N; + recent_changes = H; +} + +``` + +State after + +```html + +S with + canisters[A.canister_id] = EmptyCanister + certified_data[A.canister_id] = "" + chunk_store = () + canister_history[A.canister_id] = { + 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 = CodeUninstall; + }; + } + canister_logs[A.canister_id] = [] + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + global_timer[A.canister_id] = 0 + + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid()) + refunded_cycles = M.transferred_cycles + } · + [ ResponseMessage { + origin = Ctxt.origin + response = Reject (CANISTER_REJECT, ) + refunded_cycles = Ctxt.available_cycles + } + | Ctxt_id ↦ Ctxt ∈ S.call_contexts + , Ctxt.canister = A.canister_id + , Ctxt.needs_to_respond = true + ] + + for Ctxt_id ↦ Ctxt ∈ S.call_contexts: + if Ctxt.canister = A.canister_id: + call_contexts[Ctxt_id].deleted := true + call_contexts[Ctxt_id].needs_to_respond := false + call_contexts[Ctxt_id].available_cycles := 0 + +``` + +#### IC Management Canister: Stopping a canister + +The controllers of a canister can stop a canister. Stopping a canister goes through two steps. First, the status of the canister is set to `Stopping`; as explained above, a stopping canister rejects all incoming requests and continues processing outstanding responses. When a stopping canister has no more open call contexts, its status is changed to `Stopped` and a response is generated. Note that when processing responses, a stopping canister can make calls to other canisters and thus create new call contexts. In addition, a canister which is stopped or stopping will accept (and respond) to further `stop_canister` requests. + +We encode this behavior via three (types of) transitions: + +1. First, any `stop_canister` call sets the state of the canister to `Stopping`; we record in the IC state the origin (and cycles) of all `stop_canister` calls which arrive at the canister while it is stopping (or stopped). Note that every such `stop_canister` call can be rejected by the system at any time (the canister stays stopping in this case), e.g., if the `stop_canister` call could not be responded to for a long time. + +2. Next, when the canister has no open call contexts (so, in particular, all outstanding responses to the canister have been processed), the status of the canister is set to `Stopped`. + +3. Finally, each pending `stop_canister` call (which are encoded in the status) is responded to, to indicate that the canister is stopped. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'stop_canister' +M.arg = candid(A) +S.canister_status[A.canister_id] = Running +M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages + canister_status[A.canister_id] = Stopping [(M.origin, M.transferred_cycles)] + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + +``` + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'stop_canister' +M.arg = candid(A) +S.canister_status[A.canister_id] = Stopping Origins +M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages + canister_status[A.canister_id] = Stopping (Origins · [(M.origin, M.transferred_cycles)]) + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + +``` + +The status of a stopping canister which has no open call contexts is set to `Stopped`, and all pending `stop_canister` calls are replied to. + +Conditions + +```html + +S.canister_status[CanisterId] = Stopping Origins +∀ Ctxt_id. S.call_contexts[Ctxt_id].canister ≠ CanisterId + +``` + +State after + +```html + +S with + canister_status[CanisterId] = Stopped + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + messages = S.Messages · + [ ResponseMessage { + origin = O + response = Reply (candid()) + refunded_cycles = C + } + | (O, C) ∈ Origins + ] + +``` + +Sending a `stop_canister` message to an already stopped canister is acknowledged (i.e. responded with success) and the canister version is incremented, but is otherwise a no-op: + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'stop_canister' +M.arg = candid(A) +S.canister_status[A.canister_id] = Stopped +M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +State after + +```html + +S with + 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; + } + +``` + +Pending `stop_canister` calls may be rejected by the system at any time (the canister stays stopping in this case): + +Conditions + +```html + +S.canister_status[CanisterId] = Stopping (Older_origins · (O, C) · Younger_origins) + +``` + +State after + +```html + +S with + canister_status[CanisterId] = Stopping (Older_origins · Younger_origins) + messages = S.Messages · + ResponseMessage { + origin = O + response = Reject (SYS_TRANSIENT, ) + refunded_cycles = C + } + +``` + +#### IC Management Canister: Starting a canister + +The controllers of a canister can start a `stopped` canister. If the canister is already running, the command has no effect on the canister (except for incrementing its canister version). + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'start_canister' +M.arg = candid(A) +S.canister_status[A.canister_id] = Running or S.canister_status[A.canister_id] = Stopped +M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +State after + +```html + +S with + canister_status[A.canister_id] = Running + 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 + } + +``` + +If the status of the canister was 'stopping', then the canister status is set to `running`. The pending `stop_canister` request(s) are rejected. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'start_canister' +M.arg = candid(A) +S.canister_status[A.canister_id] = Stopping Origins +M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +State after + +```html + +S with + canister_status[A.canister_id] = Running + 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 + } · + [ ResponseMessage { + origin = O + response = Reject (CANISTER_ERROR, ) + refunded_cycles = C + } + | (O, C) ∈ Origins + ] + +``` + +#### IC Management Canister: Canister deletion + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'delete_canister' +M.arg = candid(A) +S.canister_status[A.canister_id] = Stopped +M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]] + +``` + +State after + +```html + +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) + freezing_threshold[A.canister_id] = (deleted) + canister_status[A.canister_id] = (deleted) + canister_version[A.canister_id] = (deleted) + canister_subnet[A.canister_id] = (deleted) + time[A.canister_id] = (deleted) + global_timer[A.canister_id] = (deleted) + balances[A.canister_id] = (deleted) + reserved_balances[A.canister_id] = (deleted) + reserved_balance_limits[A.canister_id] = (deleted) + wasm_memory_limit[A.canister_id] = (deleted) + wasm_memory_threshold[A.canister_id] = (deleted) + on_low_wasm_memory_hook_status[A.canister_id] = (deleted) + certified_data[A.canister_id] = (deleted) + canister_history[A.canister_id] = (deleted) + canister_log_visibility[A.canister_id] = (deleted) + canister_snapshot_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 + response = Reply (candid()) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Depositing cycles + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'deposit_cycles' +M.arg = candid(A) +A.canister_id ∈ dom(S.balances) + +``` + +State after + +```html + +S with + balances[A.canister_id] = + S.balances[A.canister_id] + M.transferred_cycles + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid()) + refunded_cycles = 0 + } + +``` + +#### IC Management Canister: Random numbers + +The management canister can produce pseudo-random bytes. It always returns a 32-byte `blob`: + +The precise guarantees around the randomness, e.g. unpredictability, are not captured in this formal semantics. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'raw_rand' +M.arg = candid() +|B| = 32 + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid(B)) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Node Metrics + +:::note + +The node metrics management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. + +::: + +The management canister returns metrics for nodes on a given subnet. The definition of the metrics values +is not captured in this formal semantics. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'node_metrics_history' +M.arg = candid(A) +R = + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid(R)) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Subnet information + +The management canister returns subnet metadata given a subnet ID. + +Conditions + +```html +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'subnet_info' +R = +``` + +State after + +```html +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid(R)) + refunded_cycles = M.transferred_cycles + } +``` + + +#### IC Management Canister: Canister creation with cycles + +This is a variant of `create_canister`, which sets the initial cycle balance based on the `amount` argument. + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'provisional_create_canister_with_cycles' +M.arg = candid(A) +is_system_assigned Canister_id +Canister_id ∉ dom(S.canisters) +(A.settings.environment_variables = null or + (|A.settings.environment_variables| ≤ MAX_ENV_VAR_COUNT and + ∀(name, value) ∈ A.settings.environment_variables: + |name| ≤ MAX_ENV_VAR_NAME_LENGTH and + |value| ≤ MAX_ENV_VAR_VALUE_LENGTH and + is_valid_utf8(name) and + is_valid_utf8(value))) + +if A.specified_id is not null: + Canister_id = A.specified_id +if A.settings.controllers is not null: + New_controllers = A.settings.controllers +else: + New_controllers = [M.caller] + +if A.settings.compute_allocation is not null: + New_compute_allocation = A.settings.compute_allocation +else: + New_compute_allocation = 0 +if A.settings.memory_allocation is not null: + New_memory_allocation = A.settings.memory_allocation +else: + New_memory_allocation = 0 +if A.settings.freezing_threshold is not null: + New_freezing_threshold = A.settings.freezing_threshold +else: + New_freezing_threshold = 2592000 +if A.settings.reserved_cycles_limit is not null: + New_reserved_balance_limit = A.settings.reserved_cycles_limit +else: + New_reserved_balance_limit = 5_000_000_000_000 +if A.settings.wasm_memory_limit is not null: + New_wasm_memory_limit = A.settings.wasm_memory_limit +else: + New_wasm_memory_limit = 0 +if A.settings.wasm_memory_threshold is not null: + New_wasm_memory_threshold = A.settings.wasm_memory_threshold +else: + New_wasm_memory_threshold = 0 +if A.settings.environment_variables is not null: + New_environment_variables = A.settings.environment_variables +else: + New_environment_variables = [] + + +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: + New_balance = DEFAULT_PROVISIONAL_CYCLES_BALANCE - 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(S', Canister_id) ≥ 0 + +New_canister_history { + total_num_changes = 1 + recent_changes = { + timestamp_nanos = CurrentTime + canister_version = 0 + origin = change_origin(M.caller, A.sender_canister_version, M.origin) + details = Creation { + controllers = New_controllers + environment_variables_hash = if A.settings.environment_variables is not null then + opt hash_of_map(A.settings.environment_variables) + else + null + } + } +} + +if A.settings.log_visibility is not null: + New_canister_log_visibility = A.settings.log_visibility +else: + New_canister_log_visibility = Controllers + +if A.settings.snapshot_visibility is not null: + New_canister_snapshot_visibility = A.settings.snapshot_visibility +else: + New_canister_snapshot_visibility = Controllers +``` + +State after + +```html + +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 + compute_allocation[Canister_id] = New_compute_allocation + memory_allocation[Canister_id] = New_memory_allocation + freezing_threshold[Canister_id] = New_freezing_threshold + balances[Canister_id] = New_balance + reserved_balances[Canister_id] = New_reserved_balance + reserved_balance_limits[Canister_id] = New_reserved_balance_limit + wasm_memory_limit[Canister_id] = New_wasm_memory_limit + wasm_memory_threshold[Canister_id] = New_wasm_memory_threshold + environment_variables[Canister_id] = New_environment_variables + on_low_wasm_memory_hook_status[Canister_id] = ConditionNotSatisfied + certified_data[Canister_id] = "" + canister_history[Canister_id] = New_canister_history + canister_log_visibility[Canister_id] = New_canister_log_visibility + canister_snapshot_visibility[Canister_id] = New_canister_snapshot_visibility + canister_logs[Canister_id] = [] + query_stats[CanisterId] = [] + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid({canister_id = Canister_id})) + refunded_cycles = M.transferred_cycles + } + canister_status[Canister_id] = Running + canister_version[Canister_id] = 0 + canister_subnet[Canister_id] = Subnet { + subnet_id : SubnetId + subnet_size : SubnetSize + } + +``` + +#### IC Management Canister: Top up canister + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'provisional_top_up_canister' +M.arg = candid(A) +A.canister_id ∈ dom(S.canisters) + +``` + +State after + +```html + +S with + balances[A.canister_id] = S.balances[A.canister_id] + A.amount + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid()) + refunded_cycles = M.transferred_cycles + } + +``` + +#### 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: + A.replace_snapshot ∈ dom(S.snapshots[A.canister_id]) +else: + |dom(S.snapshots[A.canister_id])| < MAX_SNAPSHOTS + +A.uninstall_code = null or A.uninstall_code = false + +New_snapshot = Snapshot { + source = TakenFromCanister; + take_at_timestamp = S.time[A.canister_id]; + raw_module = S.canisters[A.canister_id].raw_module; + wasm_state = S.canisters[A.canister_id].wasm_state; + chunk_store = S.chunk_store[A.canister_id]; + canister_version = S.canister_version[A.canister_id]; + certified_data = S.certified_data[A.canister_id]; + global_timer = S.global_timer[A.canister_id]; + on_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[A.canister_id]; +} +New_snapshots = S.snapshots[A.canister_id] with + A.replace_snapshot = (undefined) + Snapshot_id = New_snapshot +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, 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_snapshots + 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_snapshots([Snapshot_id → New_snapshot]); + })); + refunded_cycles = M.transferred_cycles; + } + +``` + +It is also possible to atomically uninstall code after taking a snapshot; in particular, the canister memory usage is updated atomically and thus it does not grow significantly (ignoring some potential constant overhead for certified variables which are not accounted for by canister memory usage, but are accounted for in canister snapshot memory usage). + +```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] +S.canister_history[A.canister_id] = { + total_num_changes = N; + recent_changes = H; +} + +if A.replace_snapshot is not null: + A.replace_snapshot ∈ dom(S.snapshots[A.canister_id]) +else: + |dom(S.snapshots[A.canister_id])| < MAX_SNAPSHOTS + +A.uninstall_code = true + +New_snapshot = Snapshot { + source = TakenFromCanister; + take_at_timestamp = S.time[A.canister_id]; + raw_module = S.canisters[A.canister_id].raw_module; + wasm_state = S.canisters[A.canister_id].wasm_state; + chunk_store = S.chunk_store[A.canister_id]; + canister_version = S.canister_version[A.canister_id]; + certified_data = S.certified_data[A.canister_id]; + global_timer = S.global_timer[A.canister_id]; + on_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[A.canister_id]; +} +New_snapshots = S.snapshots[A.canister_id] with + A.replace_snapshot = (undefined) + Snapshot_id = New_snapshot +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, EmptyCanister) +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_snapshots + balances[A.canister_id] = New_balance + reserved_balances[A.canister_id] = New_reserved_balance + + canisters[A.canister_id] = EmptyCanister + certified_data[A.canister_id] = "" + chunk_store = () + canister_history[A.canister_id] = { + 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 = CodeUninstall; + }; + } + canister_logs[A.canister_id] = [] + canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + global_timer[A.canister_id] = 0 + + 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_snapshots([Snapshot_id → New_snapshot]); + })); + refunded_cycles = M.transferred_cycles; + } · + [ ResponseMessage { + origin = Ctxt.origin + response = Reject (CANISTER_REJECT, ) + refunded_cycles = Ctxt.available_cycles + } + | Ctxt_id ↦ Ctxt ∈ S.call_contexts + , Ctxt.canister = A.canister_id + , Ctxt.needs_to_respond = true + ] + + for Ctxt_id ↦ Ctxt ∈ S.call_contexts: + if Ctxt.canister = A.canister_id: + call_contexts[Ctxt_id].deleted := true + call_contexts[Ctxt_id].needs_to_respond := false + call_contexts[Ctxt_id].available_cycles := 0 + +``` + +#### IC Management Canister: Load canister snapshot + + +Controllers of a canister can load a snapshot that belongs to a canister on the same subnet and also controlled by the caller. + +```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] +A.snapshot_id ∈ dom(S.snapshots[Canister_id]) +S.canister_subnet[A.canister_id].subnet_id = S.canister_subnet[Canister_id].subnet_id +M.caller ∈ S.controllers[Canister_id] +Snapshot = S.snapshots[Canister_id][A.snapshot_id] + +Mod = parse_wasm_mod(Snapshot.raw_module); + +|Snapshot.wasm_state.globals| = |Mod.initial_globals| +for i in [0..|Snapshot.wasm_state.globals|]: + if Snapshot.wasm_state.globals[i] = I32(_): + Mod.initial_globals = I32(_) + else if Snapshot.wasm_state.globals[i] = I64(_): + Mod.initial_globals = I64(_) + else if Snapshot.wasm_state.globals[i] = F32(_): + Mod.initial_globals = F32(_) + else if Snapshot.wasm_state.globals[i] = F64(_): + Mod.initial_globals = F64(_) + else if Snapshot.wasm_state.globals[i] = V128(_): + Mod.initial_globals = V128(_) + +if Snapshot.source = MetadataUpload: + if Snapshot.on_low_wasm_memory_hook_status = ConditionNotSatisfied: + HookConditionInSnapshotField = false + else: + HookConditionInSnapshotField = true + if S.wasm_memory_limit[A.canister_id] < |Snapshot.wasm_state.wasm_memory| + S.wasm_memory_threshold[A.canister_id]: + HookConditionInSnapshotState = true + else: + HookConditionInSnapshotState = false + (HookConditionInSnapshotField and HookConditionInSnapshotState) + or + ((not HookConditionInSnapshotField) and (not HookConditionInSnapshotState)) + +New_state = { + wasm_state = Snapshot.wasm_state; + raw_module = Snapshot.raw_module; + module = Mod; + public_custom_sections = parse_public_custom_sections(Snapshot.raw_module); + private_custom_sections = parse_private_custom_sections(Snapshot.raw_module); +} + +if Snapshot.source = MetadataUpload and Snapshot.global_timer is not null: + New_global_timer = Snapshot.global_timer +else: + New_global_timer = S.global_timer[A.canister_id] + +if Snapshot.source = MetadataUpload and Snapshot.on_low_wasm_memory_hook_status is not null: + New_on_low_wasm_memory_hook_status = Snapshot.on_low_wasm_memory_hook_status +else: + New_on_low_wasm_memory_hook_status = S.on_low_wasm_memory_hook_status[A.canister_id] + +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; +} +if Canister_id = A.canister_id: + From_canister_id = null +else: + From_canister_id = Canister_id +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 { + from_canister_id = From_canister_id + snapshot_id = A.snapshot_id + canister_version = Snapshot.canister_version + taken_at_timestamp = Snapshot.take_at_timestamp + source = Snapshot.source + }; + }; +} + +liquid_balance(S', A.canister_id) ≥ 0 + +``` + +State after + +```html + +S' = S with + canisters[A.canister_id] = New_state + chunk_store[A.canister_id] = Snapshot.chunk_store + certified_data[A.canister_id] = Snapshot.certified_data + global_timer[A.canister_id] = New_global_timer + on_low_wasm_memory_hook_status[A.canister_id] = New_on_low_wasm_memory_hook_status + 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: Read snapshot metadata + +Access to the metadata of a canister snapshot is determined by the canister settings `canister_snapshot_visibility`. + +```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 = 'read_canister_snapshot_metadata' +M.arg = candid(A) +(S[A.canister_id].canister_snapshot_visibility = Public) + or + (S[A.canister_id].canister_snapshot_visibility = Controllers and M.caller in S[A.canister_id].controllers) + or + (S[A.canister_id].canister_snapshot_visibility = AllowedViewers Principals and (M.caller in S[A.canister_id].controllers or M.caller in Principals)) + +A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) +Snapshot = S.snapshots[A.canister_id][A.snapshot_id] + +SnapshotMetadata = { + source = Snapshot.source; + taken_at_timestamp = Snapshot.taken_at_timestamp; + wasm_module_size = |Snapshot.raw_module|; + globals = Snapshot.wasm_state.globals; + wasm_memory_size = |Snapshot.wasm_state.wasm_memory|; + stable_memory_size = |Snapshot.wasm_state.stable_memory|; + wasm_chunk_store = [{hash: hash} | hash <- dom(Snapshot.chunk_store)] + canister_version = Snapshot.canister_version; + certified_data = Snapshot.certified_data; + global_timer = Snapshot.global_timer; + on_low_wasm_memory_hook_status = Snapshot.on_low_wasm_memory_hook_status; +} + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid(SnapshotMetadata)) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Read snapshot data + +Access to the (binary) data of a canister snapshot is determined by the canister settings `canister_snapshot_visibility`. + +```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 = 'read_canister_snapshot_data' +M.arg = candid(A) +(S[A.canister_id].canister_snapshot_visibility = Public) + or + (S[A.canister_id].canister_snapshot_visibility = Controllers and M.caller in S[A.canister_id].controllers) + or + (S[A.canister_id].canister_snapshot_visibility = AllowedViewers Principals and (M.caller in S[A.canister_id].controllers or M.caller in Principals)) + +A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) +Snapshot = S.snapshots[A.canister_id][A.snapshot_id] + +if A.kind = WasmModule { offset, size }: + offset + size <= |Snapshot.raw_module| +else if A.kind = WasmMemory { offset, size }: + offset + size <= |Snapshot.wasm_state.wasm_memory| +else if A.kind = StableMemory { offset, size }: + offset + size <= |Snapshot.wasm_state.stable_memory| +else if A.kind = WasmChunk { hash }: + hash in dom(Snapshot.chunk_store) + +if A.kind = WasmModule { offset, size }: + Chunk = Snapshot.raw_module[offset..offset+size] +else if A.kind = WasmMemory { offset, size }: + Chunk = Snapshot.wasm_state.wasm_memory[offset..offset+size] +else if A.kind = StableMemory { offset, size }: + Chunk = Snapshot.wasm_state.stable_memory[offset..offset+size] +else if A.kind = WasmChunk { hash }: + Chunk = Snapshot.chunk_store[hash] + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = Reply (candid({chunk = Chunk})) + refunded_cycles = M.transferred_cycles + } + +``` + +#### IC Management Canister: Upload canister snapshot metadata + +Only the controllers of the given canister can create a new snapshot by uploading its metadata. +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 = 'upload_canister_snapshot_metadata' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] +if A.replace_snapshot is not null: + A.replace_snapshot ∈ dom(S.snapshots[A.canister_id]) +else: + |dom(S.snapshots[A.canister_id])| < MAX_SNAPSHOTS + +New_snapshot = Snapshot { + source = MetadataUpload; + take_at_timestamp = S.time[A.canister_id]; + raw_module = [0 | _ <- [0..A.wasm_module_size]]; + wasm_state = { + wasm_memory = [0 | _ <- [0..A.wasm_memory_size]]; + stable_memory = [0 | _ <- [0..A.stable_memory_size]]; + globals = A.globals; + self_id = A.canister_id; + }; + chunk_store = []; + canister_version = S.canister_version[A.canister_id]; + certified_data = A.certified_data; + global_timer = A.global_timer; + on_low_wasm_memory_hook_status = A.on_low_wasm_memory_hook_status; +} +New_snapshots = S.snapshots[A.canister_id] with + A.replace_snapshot = (undefined) + Snapshot_id = New_snapshot +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, 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_snapshots + 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({ + snapshot_id = Snapshot_id; + })); + refunded_cycles = M.transferred_cycles; + } + +``` + +#### IC Management Canister: Upload canister snapshot data + +Only the controllers of the given canister can upload (binary) data to its 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 = 'upload_canister_snapshot_data' +M.arg = candid(A) +M.caller ∈ S.controllers[A.canister_id] + +A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) +Snapshot = S.snapshots[A.canister_id][A.snapshot_id] +Snapshot.source = MetadataUpload + +if A.kind = WasmModule { offset }: + offset + |A.chunk| <= |Snapshot.raw_module| +else if A.kind = WasmMemory { offset }: + offset + |A.chunk| <= |Snapshot.wasm_state.wasm_memory| +else if A.kind = StableMemory { offset }: + offset + |A.chunk| <= |Snapshot.wasm_state.stable_memory| +else if A.kind = WasmChunk { hash }: + |dom(Snapshot.chunk_store) ∪ {SHA-256(A.chunk)}| <= CHUNK_STORE_SIZE + +if A.kind = WasmModule { offset }: + New_raw_module = Snapshot.raw_module[0..offset] · A.chunk · Snapshot.raw_module[offset+|A.chunk|..|Snapshot.raw_module|] + New_snapshot = Snapshot with + raw_module = New_raw_module +else if A.kind = WasmMemory { offset }: + New_wasm_memory = Snapshot.wasm_memory[0..offset] · A.chunk · Snapshot.wasm_memory[offset+|A.chunk|..|Snapshot.wasm_memory|] + New_snapshot = Snapshot with + wasm_memory = New_wasm_memory +else if A.kind = StableMemory { offset }: + New_stable_memory = Snapshot.stable_memory[0..offset] · A.chunk · Snapshot.stable_memory[offset+|A.chunk|..|Snapshot.stable_memory|] + New_snapshot = Snapshot with + stable_memory = New_stable_memory +else if A.kind = WasmChunk: + New_chunk_store = Snapshot.chunk_store with + SHA-256(A.chunk) = A.chunk + New_snapshot = Snapshot with + chunk_store = New_chunk_store + +New_snapshots = S.snapshots[A.canister_id] with + Snapshot_id = New_snapshot + +Cycles_reserved = cycles_to_reserve(S, A.canister_id, S.compute_allocation[A.canister_id], S.memory_allocation[A.canister_id], New_snapshots, 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_snapshots + 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()); + refunded_cycles = M.transferred_cycles; + } + +``` + +#### IC Management Canister: List canister snapshots + +Access to the list of the existing snapshots of a canister is determined by the canister settings `canister_snapshot_visibility`. + +```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) +(S[A.canister_id].canister_snapshot_visibility = Public) + or + (S[A.canister_id].canister_snapshot_visibility = Controllers and M.caller in S[A.canister_id].controllers) + or + (S[A.canister_id].canister_snapshot_visibility = AllowedViewers Principals and (M.caller in S[A.canister_id].controllers or M.caller in Principals)) + + +Snapshots = [{ + id = Snapshot_id; + taken_at_timestamp = Snapshot.taken_at_timestamp; + total_size = memory_usage_snapshots([Snapshot_id → Snapshot]); +} | Snapshot_id → 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 that the snapshot belongs to. + +```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] +A.snapshot_id ∈ dom(S.snapshots[A.canister_id]) + +``` + +State after + +```html + +S with + S.snapshots[A.canister_id][A.snapshot_id] = (deleted) + 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. + +This "bookkeeping transition" must be immediately followed by the corresponding ["Message execution" transition](#rule-message-execution). + +Conditions + +```html + +S.messages = Older_messages · ResponseMessage RM · Younger_messages +RM.origin = FromCanister { + call_context = Ctxt_id + callback = Callback + deadline = D + } +Ctxt_id ∈ dom(S.call_contexts) +Ctxt = S.call_contexts[Ctxt_id] +not Ctxt.deleted +Ctxt.canister ∈ dom(S.balances) +D ≠ Expired _ + +Caller = if Ctxt.origin = FromUser { request = R }: + R.sender +else if Ctxt.origin = FromCanister { calling_context = Calling_ctxt, …}: + S.call_contexts[Calling_ctxt].canister +else: + ic_principal + +if Ctxt.origin = FromUser { request = R }: + if R.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): + if R.sender_info = null: + Caller_info_data = "" + Caller_info_signer = "" + else: + Caller_info_data = R.sender_info.info + Caller_info_signer = Signing_canister_id + else: + Caller_info_data = "" + Caller_info_signer = "" +else: + Caller_info_data = "" + Caller_info_signer = "" + +``` + +State after + +```html + +S with + balances[S.call_contexts[Ctxt_id].canister] = + S.balances[S.call_contexts[Ctxt_id].canister] + RM.refunded_cycles + messages = + Older_messages · + FuncMessage { + call_context = Ctxt_id + caller = Caller + caller_info_data = Caller_info_data + caller_info_signer = Caller_info_signer + receiver = S.call_contexts[Ctxt_id].canister + entry_point = Callback Callback RM.response RM.refunded_cycles + queue = Unordered + } · + Younger_messages + +``` + +If the responded call context does not exist anymore, because the canister has been uninstalled since, the refunded cycles are still added to the canister balance, but no function invocation is enqueued. + +Conditions + +```html + +S.messages = Older_messages · ResponseMessage RM · Younger_messages +RM.origin = FromCanister { + call_context = Ctxt_id + callback = Callback + deadline = D + } +Ctxt_id ∈ dom(S.call_contexts) +S.call_contexts[Ctxt_id].deleted +S.call_contexts[Ctxt_id].canister ∈ dom(S.balances) +D ≠ Expired _ + +``` + +State after + +```html + +S with + balances[S.call_contexts[Ctxt_id].canister] = + S.balances[S.call_contexts[Ctxt_id].canister] + RM.refunded_cycles + MAX_CYCLES_PER_RESPONSE + messages = Older_messages · Younger_messages + +``` + +#### Dropping expired messages {#message-timeout} + +Condition: +```html +S.messages = Older_messages · M · Younger_messages +M = ResponseMessage _ ∨ M = CallMessage _ +M.origin = FromCanister O +O.deadline = Expired _ +``` + +State after + +```html +S.messages = Older_messages · Younger_messages +``` + +#### Respond to user request + +When an ingress method call has been responded to, we can record the response in the list of queries. + +Conditions + +```html + +S.messages = Older_messages · ResponseMessage RM · Younger_messages +RM.origin = FromUser { request = M } +S.requests[M] = (Processing, ECID) + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages + requests[M] = + | (Replied R, ECID) if M.response = Reply R + | (Rejected (c, R), ECID) if M.response = Reject (c, R) + +``` + +NB: The refunded cycles, `RM.refunded_cycles` are, by construction, empty. + +#### Update call request clean up + +The IC will keep the data for a replied or rejected update `call` request around for a certain, implementation defined amount of time, to allow users to poll for the data with `read_state` requests . After that time, the data of the request will be dropped: + +Conditions + +```html + +(S.requests[M] = (Replied _, ECID)) or (S.requests[M] = (Rejected _, ECID)) + +``` + +State after + +```html + +S with + requests[M] = (Done, ECID) + +``` + +At the same or some later point, the request will be removed from the state of the IC. This must happen no earlier than the ingress expiry time set in the request. + +Conditions + +```html + +(S.requests[M] = (Replied _, _)) or (S.requests[M] = (Rejected _, _)) or (S.requests[M] = (Done, _)) +M.ingress_expiry < S.system_time + +``` + +State after + +```html + +S with + requests[M] = (deleted) + +``` + +#### Canister out of cycles + +Once a canister runs out of cycles, its code is uninstalled (cf. [IC Management Canister: Code uninstallation](#rule-uninstall)), the canister changes in the canister history are dropped (their total number is preserved), and the allocations are set to zero: + +Conditions + +```html + +S.balances[CanisterId] = 0 +S.reserved_balances[CanisterId] = 0 +S.canister_history[CanisterId] = { + total_num_changes = N; + recent_changes = H; +} + +``` + +State after + +```html + +S with + canisters[CanisterId] = EmptyCanister + snapshots[CanisterId] = null + certified_data[CanisterId] = "" + canister_history[CanisterId] = { + total_num_changes = N; + recent_changes = []; + } + canister_logs[CanisterId] = [] + canister_version[CanisterId] = S.canister_version[CanisterId] + 1 + global_timer[CanisterId] = 0 + compute_allocation[Canister_id] = 0 + memory_allocation[Canister_id] = 0 + + messages = S.messages · + [ ResponseMessage { + origin = Ctxt.origin + response = Reject (CANISTER_REJECT, ) + refunded_cycles = Ctxt.available_cycles + } + | Ctxt_id ↦ Ctxt ∈ S.call_contexts + , Ctxt.canister = CanisterId + , Ctxt.needs_to_respond = true + ] + + for Ctxt_id ↦ Ctxt ∈ S.call_contexts: + if Ctxt.canister = CanisterId: + call_contexts[Ctxt_id].deleted := true + call_contexts[Ctxt_id].needs_to_respond := false + call_contexts[Ctxt_id].available_cycles := 0 + +``` + +#### Canister renaming + +The system canister migration orchestrator (we denote its canister ID by `Canister_migration_orchestrator`) +can perform canister renaming of a canister with canister ID `Canister_id` +to a new canister ID `New_canister_id`. +The actual caller of the corresponding canister migration orchestrator's endpoint who requested canister renaming is denoted by `Caller`. +We denote the system canister migration orchestrator version when performing the renaming by `Canister_migration_orchestrator_version`. + +Conditions + +```html + +not (S.canister_subnet[Canister_id] = S.canister_subnet[New_canister_id]) + +Caller ∈ S.controllers[Canister_id] +Caller ∈ S.controllers[New_canister_id] + +Canister_migration_orchestrator ∈ S.controllers[Canister_id] +Canister_migration_orchestrator ∈ S.controllers[New_canister_id] + +S.canister_status[Canister_id] = Stopped +S.canister_status[New_canister_id] = Stopped + +∀ Snapshot_id. S.snapshots[Canister_id][Snapshot_id] = null + +S.canister_history[Canister_id] = { + total_num_changes = N; + recent_changes = H; +} + +New_canister_history = { + total_num_changes = S.canister_history[New_canister_id].total_num_changes + 1; + recent_changes = H · { + timestamp_nanos = S.time[Canister_id]; + canister_version = max(S.canister_version[New_canister_id], S.canister_version[Canister_id]) + 1; + origin = FromCanister { + canister_id = Canister_migration_orchestrator + canister_version = Canister_migration_orchestrator_version + }; + details = RenameCanister { + canister_id = Canister_id; + total_num_changes = N; + rename_to = { + canister_id = New_canister_id; + version = S.canister_version[New_canister_id]; + total_num_changes = S.canister_history[New_canister_id].total_num_changes; + }; + requested_by = Caller; + }; + }; +} + +``` + +State after + +```html + +S with + canisters[New_canister_id] = S.canisters[Canister_id] + canisters[Canister_id] = (deleted) + snapshots[New_canister_id] = {} + snapshots[Canister_id] = (deleted) + controllers[New_canister_id] = S.controllers[Canister_id] + controllers[Canister_id] = (deleted) + compute_allocation[New_canister_id] = S.compute_allocation[Canister_id] + compute_allocation[Canister_id] = (deleted) + memory_allocation[New_canister_id] = S.memory_allocation[Canister_id] + memory_allocation[Canister_id] = (deleted) + freezing_threshold[New_canister_id] = S.freezing_threshold[Canister_id] + freezing_threshold[Canister_id] = (deleted) + canister_status[New_canister_id] = S.canister_status[Canister_id] + canister_status[Canister_id] = (deleted) + canister_version[New_canister_id] = max(S.canister_version[New_canister_id], S.canister_version[Canister_id]) + 1 + canister_version[Canister_id] = (deleted) + canister_subnet[New_canister_id] = S.canister_subnet[Canister_id] + canister_subnet[Canister_id] = (deleted) + time[New_canister_id] = S.time[Canister_id] + time[Canister_id] = (deleted) + global_timer[New_canister_id] = S.global_timer[Canister_id] + global_timer[Canister_id] = (deleted) + balances[New_canister_id] = S.balances[Canister_id] + balances[Canister_id] = (deleted) + reserved_balances[New_canister_id] = S.reserved_balances[Canister_id] + reserved_balances[Canister_id] = (deleted) + reserved_balance_limits[New_canister_id] = S.reserved_balance_limits[Canister_id] + reserved_balance_limits[Canister_id] = (deleted) + wasm_memory_limit[New_canister_id] = S.wasm_memory_limit[Canister_id] + wasm_memory_limit[Canister_id] = (deleted) + wasm_memory_threshold[New_canister_id] = S.wasm_memory_threshold[Canister_id] + wasm_memory_threshold[Canister_id] = (deleted) + environment_variables[New_canister_id] = S.environment_variables[Canister_id] + environment_variables[Canister_id] = (deleted) + on_low_wasm_memory_hook_status[New_canister_id] = S.on_low_wasm_memory_hook_status[Canister_id] + on_low_wasm_memory_hook_status[Canister_id] = (deleted) + certified_data[New_canister_id] = S.certified_data[Canister_id] + certified_data[Canister_id] = (deleted) + canister_history[New_canister_id] = New_canister_history + canister_history[Canister_id] = (deleted) + canister_log_visibility[New_canister_id] = S.canister_log_visibility[Canister_id] + canister_log_visibility[Canister_id] = (deleted) + canister_snapshot_visibility[New_canister_id] = S.canister_snapshot_visibility[Canister_id] + canister_snapshot_visibility[Canister_id] = (deleted) + canister_logs[New_canister_id] = S.canister_logs[Canister_id] + canister_logs[Canister_id] = (deleted) + query_stats[New_canister_id] = S.query_stats[Canister_id] + query_stats[Canister_id] = (deleted) + +``` + +#### Time progressing, cycle consumption, canister version increments and subnet admins updates + +Time progresses. Abstractly, it does so independently for each canister, and in unspecified intervals. + +Conditions + +```html + +T0 = S.time[CanisterId] +T1 > T0 + +``` + +State after + +```html + +S with + time[CanisterId] = T1 + +``` + +The canister cycle balances similarly deplete at an unspecified rate, but stay non-negative. +If the canister has a positive reserved balance, then the reserved balance depletes before the main balance: + +Conditions + +```html +R0 = S.reserved_balances[CanisterId] +0 ≤ R1 < R0 + +``` + +State after + +```html + +S with + reserved_balances[CanisterId] = R1 + +``` + +Once the reserved balance reaches zero, then the main balance starts depleting: + +Conditions + +```html +S.reserved_balances[CanisterId] = 0 +B0 = S.balances[CanisterId] +0 ≤ B1 < B0 + +``` + +State after + +```html + +S with + balances[CanisterId] = B1 + +``` + +Similarly, the system time, used to expire requests, progresses: + +Conditions + +```html + +T0 = S.system_time +T1 > T0 + +``` + +State after + +```html + +S with + system_time = T1 + +``` + +Additionally, the canister version can be incremented arbitrarily: + +Conditions + +```html + +N0 = S.canister_version[CanisterId] +N1 > N0 + +``` + +State after + +```html + +S with + canister_version[CanisterId] = N1 + +``` + +Finally, subnet admins can be changed arbirtrarily: + +Conditions + +```html + +SA0 = S.subnet_admins +SA1 != SA0 + +``` + +State after + +```html + +S with + subnet_admins = SA1 + +``` + +:::note + +In production, subnet admins can be set via the Subnet Rental Canister which is not modeled in this document. + +#### Trimming canister history + +The list of canister changes can be trimmed, but the total number of recorded canister changes cannot be altered. At least 20 changes are guaranteed to remain in the list of changes. + +Conditions + +```html + +S.canister_history[CanisterId] = { + total_num_changes = N; + recent_changes = Older_changes · Newer_changes; + } +|Newer_changes| ≥ 20 + +``` + +State after + +```html + +S with + canister_history[CanisterId] = { + total_num_changes = N; + recent_changes = Newer_changes; + } + +``` + +#### Trimming canister logs + +Canister logs can be trimmed if their total length exceeds 4KiB. + +Conditions + +```html + +S.canister_logs[CanisterId] = Older_logs · Newer_logs +SUM { |l| | l <- Older_logs } > 4KiB + +``` + +State after + +```html + +S with + canister_logs[CanisterId] = Newer_logs + +``` + +#### IC Management Canister: Canister logs (query call) {#ic-mgmt-canister-fetch-canister-logs} + +This section specifies management canister query calls. +They are calls to `/api/v3/canister//query` +with CBOR content `Q` such that `Q.canister_id = ic_principal`. + +The management canister offers the method `fetch_canister_logs` +that can be called as a query call and +returns logs of a requested canister. + +Submitted request to `/api/v3/canister//query` + +```html + +E : Envelope + +``` + +Conditions + +```html + +E.content = CanisterQuery Q +Q.canister_id = ic_principal +Q.method_name = 'fetch_canister_logs' +|Q.nonce| <= 32 +is_effective_canister_id(E.content, ECID) +S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id +Q.arg = candid(A) +A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) +if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: + if not (Q.sender_info = null): + verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) + Q.sender_info.signer = Signing_canister_id +else: + Q.sender_info = null +(S[A.canister_id].canister_log_visibility = Public) + or + (S[A.canister_id].canister_log_visibility = Controllers and Q.sender in S[A.canister_id].controllers) + or + (S[A.canister_id].canister_log_visibility = AllowedViewers Principals and (Q.sender in S[A.canister_id].controllers or Q.sender in Principals)) + +``` + +Query response `R`: + +```html + +{status: "replied"; reply: {arg: candid(S.canister_logs[A.canister_id])}, signatures: Sigs} + +``` + +where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: + +```html + +verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" + +``` + +#### IC Management Canister: List canisters (query call) {#ic-mgmt-canister-list-canisters} + +This section specifies the `list_canisters` management canister query call. +It is a call to `/api/v3/canister//query` +with CBOR content `Q` such that `Q.canister_id = ic_principal`. + +The management canister offers the method `list_canisters` +that can be called as a query call by subnet admins and +returns the list of all canisters on the subnet as consecutive canister ID ranges. + +Submitted request to `/api/v3/canister//query` + +```html + +E : Envelope + +``` + +Conditions + +```html + +E.content = CanisterQuery Q +Q.canister_id = ic_principal +Q.method_name = 'list_canisters' +|Q.nonce| <= 32 +is_effective_canister_id(E.content, ECID) +S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id +verify_envelope(E, Q.sender, S.system_time) +Q.sender ∈ S.subnet_admins[S.canister_subnet[ECID]] + +``` + +Query response `R`: + +```html + +{status: "replied"; reply: {arg: candid({canisters: CanisterIdRanges})}, signatures: Sigs} + +``` + +where `CanisterIdRanges` is the list of all canister IDs on the subnet encoded as consecutive canister ID ranges (excluding deleted canisters), and the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: + +```html + +verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" + +``` + +#### Query call {#query-call} + +This section specifies query calls `Q` whose `Q.canister_id` is a non-empty canister `S.canisters[Q.canister_id]`. Query calls to the management canister, i.e., `Q.canister_id = ic_principal`, are specified in Sections [Canister status](#ic-management-canister-canister-status), [Canister logs](#ic-mgmt-canister-fetch-canister-logs), and [List canisters](#ic-mgmt-canister-list-canisters). + +Canister query calls to `/api/v3/canister//query` can be executed directly. They can only be executed against non-empty canisters which have a status of `Running` and are also not frozen. + +In query and composite query methods evaluated on the target canister of the query call, a certificate is provided to the canister that is valid, contains a current state tree (or "recent enough"; the specification is currently vague about how old the certificate may be), and reveals the canister's [Certified Data](./canister-interface.md#system-api-certified-data). + +Composite query methods can call query methods and composite query methods up to a maximum depth `MAX_CALL_DEPTH_COMPOSITE_QUERY` of the call graph. The total amount of cycles consumed by executing a (composite) query method and all (transitive) calls it makes must be at most `MAX_CYCLES_PER_QUERY`. This limit applies in addition to the limit `MAX_CYCLES_PER_MESSAGE` for executing a single (composite) query method and `MAX_CYCLES_PER_RESPONSE` for executing a single callback of a (composite) query method. + +We define an auxiliary method that handles calls from composite query methods by performing a call graph traversal. It can also be (trivially) invoked for query methods that do not make further calls. +``` +composite_query_helper(S, Cycles, Depth, Root_canister_id, Caller, Caller_info_data, Caller_info_signer, Canister_id, Method_name, Arg) = + let Mod = S.canisters[Canister_id].module + let Cert <- { Cert | verify_cert(Cert) and + lookup(["canister", Canister_id, "certified_data"], Cert) = Found S.certified_data[Canister_id] and + lookup(["time"], Cert) = Found S.system_time // or "recent enough" + } + if Canister_id ≠ Root_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]; + reserved_balance_limit = S.reserved_balance_limits[Canister_id]; + compute_allocation = S.compute_allocation[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_snapshots = memory_usage_snapshots(S.snapshots[Canister_id]); + freezing_threshold = S.freezing_threshold[Canister_id]; + subnet_id = S.canister_subnet[Canister_id].subnet_id; + subnet_size = S.canister_subnet[Canister_id].subnet_size; + certificate = Cert; + status = simple_status(S.canister_status[Canister_id]); + canister_version = S.canister_version[Canister_id]; + } + if S.canisters[Canister_id] ≠ EmptyCanister and + S.canister_status[Canister_id] = Running and + (Method_name ∈ dom(Mod.query_methods) or Method_name ∈ dom(Mod.composite_query_methods)) and + Cycles >= MAX_CYCLES_PER_MESSAGE + 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, Canister_id) < 0 + then + Return (Reject (SYS_TRANSIENT, ), Cycles, S) + let R = F(Arg, Caller, Caller_info_data, Caller_info_signer, Env)(W) + if R = Trap trap + then Return (Reject (CANISTER_ERROR, ), Cycles - trap.cycles_used, S) + else if R = Return {new_state = W'; new_calls = Calls; response = Response; cycles_used = Cycles_used} + then + W := W' + if Cycles_used > MAX_CYCLES_PER_MESSAGE + then + Return (Reject (CANISTER_ERROR, ), Cycles - MAX_CYCLES_PER_MESSAGE, S) // single message execution out of cycles + Cycles := Cycles - Cycles_used + if Response = NoResponse + then + while Calls ≠ [] + do + if Depth = MAX_CALL_DEPTH_COMPOSITE_QUERY + then + Return (Reject (CANISTER_ERROR, ), Cycles, S) // max call graph depth exceeded + let Calls' · Call · Calls'' = Calls + Calls := Calls' · Calls'' + if S.canister_subnet[Canister_id].subnet_id ≠ S.canister_subnet[Call.callee].subnet_id + then + Return (Reject (CANISTER_ERROR, ), Cycles, S) // calling to another subnet + let (Response', Cycles', S') = composite_query_helper(S, Cycles, Depth + 1, Root_canister_id, Canister_id, "", "", Call.callee, Call.method_name, Call.arg) + Cycles := Cycles' + S := S' + if Cycles < MAX_CYCLES_PER_RESPONSE + then + Return (Reject (CANISTER_ERROR, ), Cycles, S) // composite query out of cycles + Env.Cert = NoCertificate // no certificate available in composite query callbacks + let F' = Mod.composite_callbacks(Call.callback, Caller, Caller_info_data, Caller_info_signer, Response', Env) + let R'' = F'(W') + if R'' = Trap trap'' + then Return (Reject (CANISTER_ERROR, ), Cycles - trap''.cycles_used, S) + else if R'' = Return {new_state = W''; new_calls = Calls''; response = Response''; cycles_used = Cycles_used''} + then + W := W'' + if Cycles_used'' > MAX_CYCLES_PER_RESPONSE + then + Return (Reject (CANISTER_ERROR, ), Cycles - MAX_CYCLES_PER_RESPONSE, S) // single message execution out of cycles + Cycles := Cycles - Cycles_used'' + if Response'' = NoResponse + then + Calls := Calls'' · Calls + else + Return (Response'', Cycles, S) + Return (Reject (CANISTER_ERROR, ), Cycles, S) // canister did not respond + else + Return (Response, Cycles, S) + else + Return (Reject (CANISTER_ERROR, ), Cycles, S) +``` + +Submitted request to `/api/v3/canister//query` + +```html + +E : Envelope + +``` + +Conditions + +```html + +E.content = CanisterQuery Q +Q.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) +if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: + if not (Q.sender_info = null): + verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) + Q.sender_info.signer = Signing_canister_id +else: + Q.sender_info = null +|Q.nonce| <= 32 +is_effective_canister_id(E.content, ECID) +S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id + +if Q.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed): + if Q.sender_info = null: + Caller_info_data = "" + Caller_info_signer = "" + else: + Caller_info_data = Q.sender_info.info + Caller_info_signer = Signing_canister_id +else: + Caller_info_data = "" + Caller_info_signer = "" + +``` + +Query response `R`: + +- if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Caller_info_data, Caller_info_signer, Q.canister_id, Q.method_name, Q.arg) = (Reject (RejectCode, RejectMsg), _, S')` then + ``` + {status: "rejected"; reject_code: RejectCode; reject_message: RejectMsg; error_code: , signatures: Sigs} + ``` + +- Else if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Caller_info_data, Caller_info_signer, Q.canister_id, Q.method_name, Q.arg) = (Reply Res, _, S')` then + ``` + {status: "replied"; reply: {arg: Res}, signatures: Sigs} + ``` + +where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: + +```html + +verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" + +``` + +State after + +```html + +S' with + query_stats[Q.receiver] = S'.query_stats[Q.receiver] · { + timestamp = S'.time[Q.receiver] + num_instructions = + request_payload_bytes = |Q.Arg| + response_payload_bytes = + if R.status = "rejected" then |R.reject_message| + else |R.reply.arg| + } + +``` + +#### Certified state reads + +:::note + +Requesting paths with the prefix `/subnet` at `/api/v3/canister//read_state` might be deprecated in the future. Hence, users might want to point their requests for paths with the prefix `/subnet` to `/api/v3/subnet//read_state`. + +On the IC mainnet, the root subnet ID `tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe` can be used to retrieve the list of all IC mainnet's subnets by requesting the prefix `/subnet` at `/api/v3/subnet/tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe/read_state`. + +::: + +The user can read elements of the *state tree*, using a `read_state` request to `/api/v3/canister//read_state` or `/api/v3/subnet//read_state`. + +Submitted request to `/api/v3/canister//read_state` + +```html + +E : Envelope + +``` + +Conditions + +```html + +E.content = ReadState RS +TS = verify_envelope(E, RS.sender, S.system_time) +|E.content.nonce| <= 32 +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 + +``` + +Read response +A record with + +- `{certificate: C}` + +The predicate `may_read_path_for_canister` is defined as follows, implementing the access control outlined in [Request: Read state](./https-interface.md#http-read-state): +``` +may_read_path_for_canister(S, _, ["time"]) = True +may_read_path_for_canister(S, _, ["subnet"]) = True +may_read_path_for_canister(S, _, ["subnet", sid]) = True +may_read_path_for_canister(S, _, ["subnet", sid, "public_key"]) = True +may_read_path_for_canister(S, _, ["subnet", sid, "type"]) = True +may_read_path_for_canister(S, _, ["subnet", sid, "canister_ranges"]) = sid == root_subnet_id +may_read_path_for_canister(S, _, ["subnet", sid, "node"]) = True +may_read_path_for_canister(S, _, ["subnet", sid, "node", nid]) = True +may_read_path_for_canister(S, _, ["subnet", sid, "node", nid, "public_key"]) = True +may_read_path_for_canister(S, _, ["request_status", Rid]) = +may_read_path_for_canister(S, _, ["request_status", Rid, "status"]) = +may_read_path_for_canister(S, _, ["request_status", Rid, "reply"]) = +may_read_path_for_canister(S, _, ["request_status", Rid, "reject_code"]) = +may_read_path_for_canister(S, _, ["request_status", Rid, "reject_message"]) = +may_read_path_for_canister(S, _, ["request_status", Rid, "error_code"]) = + ∀ (R ↦ (_, ECID')) ∈ dom(S.requests). hash_of_map(R) = Rid => RS.sender == R.sender ∧ ECID == ECID' +may_read_path_for_canister(S, _, ["canister", cid, "module_hash"]) = cid == ECID +may_read_path_for_canister(S, _, ["canister", cid, "controllers"]) = cid == ECID +may_read_path_for_canister(S, _, ["canister", cid, "metadata", name]) = cid == ECID ∧ UTF8(name) ∧ + (cid ∉ dom(S.canisters[cid]) ∨ + S.canisters[cid] = EmptyCanister ∨ + name ∉ (dom(S.canisters[cid].public_custom_sections) ∪ dom(S.canisters[cid].private_custom_sections)) ∨ + name ∈ dom(S.canisters[cid].public_custom_sections) ∨ + (name ∈ dom(S.canisters[cid].private_custom_sections) ∧ RS.sender ∈ S.controllers[cid]) + ) +may_read_path_for_canister(S, _, _) = False +``` + +where `UTF8(name)` holds if `name` is encoded in UTF-8. + +Submitted request to `/api/v3/subnet//read_state` + +```html + +E : Envelope + +``` + +Conditions + +```html + +E.content = ReadState RS +verify_envelope(E, RS.sender, S.system_time) +|E.content.nonce| <= 32 +S.system_time <= RS.ingress_expiry +∀ path ∈ RS.paths. may_read_path_for_subnet(S, RS.sender, path) + +``` + +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](./https-interface.md#http-read-state): +``` +may_read_path_for_subnet(S, _, ["time"]) = True +may_read_path_for_subnet(S, _, ["canister_ranges", sid]) = True +may_read_path_for_subnet(S, _, ["subnet"]) = True +may_read_path_for_subnet(S, _, ["subnet", sid]) = True +may_read_path_for_subnet(S, _, ["subnet", sid, "public_key"]) = True +may_read_path_for_subnet(S, _, ["subnet", sid, "type"]) = True +may_read_path_for_subnet(S, _, ["subnet", sid, "canister_ranges"]) = sid == root_subnet_id +may_read_path_for_subnet(S, _, ["subnet", sid, "metrics"]) = sid == +may_read_path_for_subnet(S, _, ["subnet", sid, "node"]) = True +may_read_path_for_subnet(S, _, ["subnet", sid, "node", nid]) = True +may_read_path_for_subnet(S, _, ["subnet", sid, "node", nid, "public_key"]) = True +may_read_path_for_subnet(S, _, _) = False +``` +The response is a certificate `cert`, as specified in [Certification](./certification.md#certification), which passes `verify_cert` (assuming `S.root_key` as the root of trust), and where for every `path` documented in [The system state tree](./index.md#state-tree) that has a path in `RS.paths` or `["time"]` as a prefix, we have +``` +lookup_in_tree(path, cert.tree) = lookup_in_tree(path, state_tree(S)) +``` +where `state_tree` constructs a labeled tree from the IC state `S` and the (so far underspecified) set of subnets `subnets`, as per [The system state tree](./index.md#state-tree) +``` +state_tree(S) = { + "time": S.system_time; + "canister_ranges": { subnet_id : { canister_id : ranges | the lexicographically sorted list of ranges in subnet_ranges is split into chunks starting at canister_id } | (subnet_id, _, subnet_ranges, _) ∈ subnets }; + "subnet": { subnet_id : { "public_key" : subnet_pk; "type" : ; "metrics" : ; "node": { node_id : { "public_key" : node_pk } | (node_id, node_pk) ∈ subnet_nodes } } | (subnet_id, subnet_pk, subnet_ranges, subnet_nodes) ∈ subnets }; + "subnet": { subnet_id : { "canister_ranges" : subnet_ranges } | (subnet_id, _, subnet_ranges, _) ∈ subnets ∧ subnet_id == root_subnet_id }; + "request_status": { request_id(R): request_status_tree(T) | (R ↦ (T, _)) ∈ S.requests }; + "canister": + { canister_id : + { "module_hash" : SHA256(C.raw_module) | if C ≠ EmptyCanister } ∪ + { "controllers" : CBOR(S.controllers[canister_id]) } ∪ + { "metadata": { name: blob | (name, blob) ∈ S.canisters[canister_id].public_custom_sections ∪ S.canisters[canister_id].private_custom_sections } } + | (canister_id, C) ∈ S.canisters }; +} + +request_status_tree(Received) = + { "status": "received" } +request_status_tree(Processing) = + { "status": "processing" } +request_status_tree(Rejected (code, msg)) = + { "status": "rejected"; "reject_code": code; "reject_message": msg; "error_code": } +request_status_tree(Replied arg) = + { "status": "replied"; "reply": arg } +request_status_tree(Done) = + { "status": "done" } +``` + +and where `lookup_in_tree` is a function that returns `Found v` for a value `v`, `Absent`, or `Error`, appropriately. See the Section [Lookup](./certification.md#lookup) for more details. + +### Abstract Canisters to System API {#concrete-canisters} + +In Section [Abstract canisters](#abstract-canisters) we introduced an abstraction over the interface to a canister, to avoid cluttering the abstract specification of the Internet Computer from WebAssembly details. In this section, we will fill the gap and explain how the abstract canister interface maps to the [concrete System API](./canister-interface.md#system-api) and the WebAssembly concepts as defined in the [WebAssembly specification](https://webassembly.github.io/spec/core/index.html). + +#### The concrete `Callback` + +The abstract `Callback` type above models an entry point for responses: +``` +I ∈ {i32, i64} +Closure = { + fun : I, + env : I, +} +Callback = { + on_reply : Closure; + on_reject : Closure; + on_cleanup : Closure | NoClosure; +} +``` + +#### The execution state + +We can model the execution of WebAssembly functions as stateful functions that have access to the WASM memory (a.k.a. heap) and (exported or mutable) globals in `WasmState`. In order to also model the behavior of the system imports, which have access to additional data structures, we extend the state as follows: +``` +Params = { + arg : NoArg | Blob; + caller : Principal; + caller_info_data : Blob; + caller_info_signer : Blob; + reject_code : 0 | SYS_FATAL | SYS_TRANSIENT | …; + reject_message : Text; + sysenv : Env; + cycles_refunded : Nat; + method_name : NoText | Text; + deadline : NoDeadline | Timestamp; +} +ExecutionState = { + wasm_state : WasmState; + params : Params; + response : NoResponse | Response; + cycles_accepted : Nat; + cycles_available : Nat; + cycles_used : Nat; + balance : Nat; + reply_params : { arg : Blob }; + pending_call : MethodCall | NoPendingCall; + calls : List MethodCall; + new_certified_data : NoCertifiedData | Blob; + new_global_timer : NoGlobalTimer | Nat; + ingress_filter : Accept | Reject; + context : I | G | U | Q | CQ | Ry | Rt | CRy | CRt | C | CC | F | T | s; +} +``` + +This allows us to model WebAssembly functions, including host-provided imports, as functions with implicit mutable access to an `ExecutionState`, dubbed *execution functions*. Syntactically, we express this using an implicit argument of type `ref ExecutionState` in angle brackets (e.g. `func(x)` for the invocation of a WebAssembly function with type `(x : i32) -> ()`). The lifetime of the `ExecutionState` data structure is that of one such function invocation. + +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_snapshots, + es.params.sysenv.subnet_size, + ) + ) +``` + +- For more convenience when creating a new `ExecutionState`, we define the following partial records: + ``` + empty_params = { + arg = NoArg; + caller = ic_principal; + caller_info_data = ""; + caller_info_signer = ""; + reject_code = 0; + reject_message = ""; + sysenv = (undefined); + cycles_refunded = 0; + method_name = NoText; + deadline = NoDeadline; + } + empty_execution_state = { + wasm_state = (undefined); + params = (undefined); + response = NoResponse; + cycles_accepted = 0; + cycles_available = 0; + cycles_used = 0; + balance = 0; + reply_params = { arg = "" }; + pending_call = NoPendingCall; + calls = []; + new_certified_data = NoCertifiedData; + new_global_timer = NoGlobalTimer; + ingress_filter = Reject; + context = (undefined); + } + ``` + +#### The concrete `CanisterModule` + +Finally, we can specify the abstract `CanisterModule` that models a concrete WebAssembly module. + +- We define the initial values `initial_globals` of the (exported or mutable) globals declared in the WebAssembly module. + +- We define a helper `table` which is an array of all functions of the WebAssembly module listed in its (unique according to Section [WebAssembly module requirements](./canister-interface.md#system-api-module)) table. + +- We define a helper function + ``` + start : (WasmState) -> Trap { cycles_used : Nat; } | Return { + new_state : WasmState; + cycles_used : Nat; + } + ``` + + modelling execution of a potential `(start)` function. + + If the WebAssembly module does not export a function called under the name `start`, then + ``` + start = λ (wasm_state) → + Return { + new_state = wasm_state; + cycles_used = 0; + } + ``` + + Otherwise, if the WebAssembly module exports a function `func` under the name `start`, it is + ``` + start = λ (wasm_state) → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + context = s; + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + Return { + new_state = es.wasm_state; + cycles_used = es.cycles_used; + } + ``` + + Note that `params` are undefined in the `(start)` function's execution state which is fine because the System API does not have access to that part of the execution state during the execution of the `(start)` function. + +- The `init` field of the `CanisterModule` is defined as follows: + + If the WebAssembly module does not export a function called under the name `canister_init`, then + ``` + init = λ (self_id, arg, caller, sysenv) → + match start({wasm_memory = ""; stable_memory = ""; globals = initial_globals; self_id = self_id;}) with + Trap trap → Trap trap + Return res → Return { + new_state = res.wasm_state; + new_certified_data = NoCertifiedData; + new_global_timer = NoGlobalTimer; + cycles_used = res.cycles_used; + } + ``` + + Otherwise, if the WebAssembly module exports a function `func` under the name `canister_init`, it is + ``` + init = λ (self_id, arg, caller, sysenv) → + match start({wasm_memory = ""; stable_memory = ""; globals = initial_globals; self_id = self_id;}) with + Trap trap → Trap trap + Return res → + let es = ref {empty_execution_state with + wasm_state = res.wasm_state + params = empty_params with { + arg = arg; + caller = caller; + sysenv = sysenv with { + balance = sysenv.balance - res.cycles_used + } + } + balance = sysenv.balance - res.cycles_used + context = I + } + try func() with Trap then Trap {cycles_used = res.cycles_used + es.cycles_used;} + Return { + new_state = es.wasm_state; + new_certified_data = es.new_certified_data; + new_global_timer = es.new_global_timer; + cycles_used = res.cycles_used + es.cycles_used; + } + ``` + +- The `pre_upgrade` field of the `CanisterModule` is defined as follows: + + If the WebAssembly module does not export a function called under the name `canister_pre_upgrade`, then it simply returns the current state: + ``` + pre_upgrade = λ (old_state, caller, sysenv) → Return {new_state = old_state; new_certified_data = NoCertifiedData; cycles_used = 0;} + ``` + + Otherwise, if the WebAssembly module exports a function `func` under the name `canister_pre_upgrade`, it is + ``` + pre_upgrade = λ (old_state, caller, sysenv) → + let es = ref {empty_execution_state with + wasm_state = old_state + params = empty_params with { caller = caller; sysenv } + balance = sysenv.balance + context = G + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + Return { + new_state = es.wasm_state; + new_certified_data = es.new_certified_data; + cycles_used = es.cycles_used; + } + ``` + +- The `post_upgrade` field of the `CanisterModule` is defined as follows: + + If the WebAssembly module does not export a function called under the name `canister_post_upgrade`, then + ``` + post_upgrade = λ (wasm_state, arg, caller, sysenv) → + match start(wasm_state) with + Trap trap → Trap trap + Return res → Return { + new_state = res.wasm_state; + new_certified_data = NoCertifiedData; + new_global_timer = NoGlobalTimer; + cycles_used = res.cycles_used; + } + ``` + + Otherwise, if the WebAssembly module exports a function `func` under the name `canister_post_upgrade`, it is + ``` + post_upgrade = λ (wasm_state, arg, caller, sysenv) → + match start(wasm_state) with + Trap trap → Trap trap + Return res → + let es = ref {empty_execution_state with + wasm_state = res.wasm_state + params = empty_params with { + arg = arg; + caller = caller; + sysenv = sysenv with { + balance = sysenv.balance - res.cycles_used + } + } + balance = sysenv.balance - res.cycles_used + context = I + } + try func() with Trap then Trap {cycles_used = res.cycles_used + es.cycles_used;} + Return { + new_state = es.wasm_state; + new_certified_data = es.new_certified_data; + new_global_timer = es.new_global_timer; + cycles_used = res.cycles_used + es.cycles_used; + } + ``` + +- The partial map `update_methods` of the `CanisterModule` is defined for all method names `method` for which the WebAssembly program exports a function `func` named `canister_update `, and has value + ``` + update_methods[method] = λ (arg, caller, caller_info_data, caller_info_signer, deadline, sysenv, available) → λ wasm_state → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = empty_params with { + arg = arg; + caller = caller; + caller_info_data = caller_info_data; + caller_info_signer = caller_info_signer; + deadline = deadline; + sysenv; + } + balance = sysenv.balance + cycles_available = available; + context = U + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + discard_pending_call() + Return { + new_state = es.wasm_state; + new_calls = es.calls; + new_certified_data = es.new_certified_data; + new_global_timer = es.new_global_timer; + response = es.response; + cycles_accepted = es.cycles_accepted; + cycles_used = es.cycles_used; + } + ``` + +- The partial map `query_methods` of the `CanisterModule` is defined for all method names `method` for which the WebAssembly program exports a function `func` named `canister_query `, and has value + ``` + query_methods[method] = λ (arg, caller, caller_info_data, caller_info_signer, sysenv, available) → λ wasm_state → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = empty_params with { + arg = arg; + caller = caller; + caller_info_data = caller_info_data; + caller_info_signer = caller_info_signer; + sysenv + } + balance = sysenv.balance + cycles_available = available + context = Q + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + Return { + response = es.response; + cycles_accepted = es.cycles_accepted; + cycles_used = es.cycles_used; + } + ``` + + By construction, the (possibly modified) `es.wasm_state` is discarded. + +- The partial map `composite_query_methods` of the `CanisterModule` is defined for all method names `method` for which the WebAssembly program exports a function `func` named `canister_composite_query `, and has value + ``` + composite_query_methods[method] = λ (arg, caller, caller_info_data, caller_info_signer, sysenv) → λ wasm_state → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = empty_params with { + arg = arg; + caller = caller; + caller_info_data = caller_info_data; + caller_info_signer = caller_info_signer; + sysenv + } + balance = sysenv.balance + context = CQ + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + discard_pending_call() + Return { + new_state = es.wasm_state; + new_calls = es.calls; + response = es.response; + cycles_used = es.cycles_used; + } + ``` + +- The function `heartbeat` of the `CanisterModule` is defined if the WebAssembly program exports a function `func` named `canister_heartbeat`, and has value + ``` + heartbeat = λ (sysenv) → λ wasm_state → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = empty_params with { arg = NoArg; caller = ic_principal; sysenv } + balance = sysenv.balance + context = T + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + discard_pending_call() + Return { + new_state = es.wasm_state; + new_calls = es.calls; + new_certified_data = es.certified_data; + new_global_timer = es.new_global_timer; + cycles_used = es.cycles_used; + } + ``` + + otherwise it is + +```html + +heartbeat = λ (sysenv) → λ wasm_state → Trap {cycles_used = 0;} + +``` + +- The function `global_timer` of the `CanisterModule` is defined if the WebAssembly program exports a function `func` named `canister_global_timer`, and has value + ``` + global_timer = λ (sysenv) → λ wasm_state → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = empty_params with { arg = NoArg; caller = ic_principal; sysenv } + balance = sysenv.balance + context = T + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + discard_pending_call() + Return { + new_state = es.wasm_state; + new_calls = es.calls; + new_certified_data = es.certified_data; + new_global_timer = es.new_global_timer; + cycles_used = es.cycles_used; + } + ``` + + otherwise it is + +```html + +global_timer = λ (sysenv) → λ wasm_state → Trap {cycles_used = 0;} + +``` + +- The function `on_low_wasm_memory` of the `CanisterModule` is defined if the WebAssembly program exports a function `func` named `canister_on_low_wasm_memory`, and has value + ``` + on_low_wasm_memory = λ (sysenv) → λ wasm_state → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = empty_params with { arg = NoArg; caller = ic_principal; sysenv } + balance = sysenv.balance + context = T + } + try func() with Trap then Trap {cycles_used = es.cycles_used;} + discard_pending_call() + Return { + new_state = es.wasm_state; + new_calls = es.calls; + new_certified_data = es.certified_data; + new_global_timer = es.new_global_timer; + cycles_used = es.cycles_used; + } + ``` + + otherwise it is + + ```html + on_low_wasm_memory = λ (sysenv) → λ wasm_state → Trap {cycles_used = 0;} + ``` + +- The function `callbacks` of the `CanisterModule` is defined as follows + ``` + I ∈ {i32, i64} + callbacks = λ(callbacks, caller, caller_info_data, caller_info_signer, response, deadline, refunded_cycles, sysenv, available) → λ wasm_state → + let params0 = empty_params with { + caller = caller; + caller_info_data = caller_info_data; + caller_info_signer = caller_info_signer; + sysenv; + cycles_refunded = refund_cycles; + deadline; + } + let (fun, env, params, context) = match response with + Reply data -> + (callbacks.on_reply.fun, callbacks.on_reply.env, + { params0 with data}, Ry) + Reject (reject_code, reject_message)-> + (callbacks.on_reject.fun, callbacks.on_reject.env, + { params0 with reject_code; reject_message}, Rt) + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = params; + balance = sysenv.balance; + cycles_available = available; + context = context; + } + try + if fun > |table| then Trap + let func = table[fun] + if typeof(func) ≠ func (I) -> () then Trap + func(env) + discard_pending_call() + Return { + new_state = es.wasm_state; + new_calls = es.calls; + new_certified_data = es.certified_data; + new_global_timer = es.new_global_timer; + response = es.response; + cycles_accepted = es.cycles_accepted; + cycles_used = es.cycles_used; + } + with Trap + if callbacks.on_cleanup = NoClosure then Trap {cycles_used = es.cycles_used;} + if callbacks.on_cleanup.fun > |table| then Trap {cycles_used = es.cycles_used;} + let func = table[callbacks.on_cleanup.fun] + if typeof(func) ≠ func (I) -> () then Trap {cycles_used = es.cycles_used;} + + let es' = ref { empty_execution_state with + wasm_state = wasm_state; + params = params; + balance = sysenv.balance - es.cycles_used; + context = C; + } + try func(callbacks.on_cleanup.env) with Trap then Trap {cycles_used = es.cycles_used + es'.cycles_used;} + Return { + new_state = es'.wasm_state; + new_calls = []; + new_certified_data = NoCertifiedData; + new_global_timer = es'.new_global_timer; + response = NoResponse; + cycles_accepted = 0; + cycles_used = es.cycles_used + es'.cycles_used; + } + ``` + + Note that if the initial callback handler traps, the cleanup callback (if present) is executed, and the canister has the chance to update its state. + +- The function `composite_callbacks` of the `CanisterModule` is defined as follows + ``` + I ∈ {i32, i64} + composite_callbacks = λ(callbacks, caller, caller_info_data, caller_info_signer, response, sysenv) → λ wasm_state → + let params0 = empty_params with { + caller = caller; + caller_info_data = caller_info_data; + caller_info_signer = caller_info_signer; + sysenv + } + let (fun, env, params, context) = match response with + Reply data -> + (callbacks.on_reply.fun, callbacks.on_reply.env, + { params0 with data}, CRy) + Reject (reject_code, reject_message)-> + (callbacks.on_reject.fun, callbacks.on_reject.env, + { params0 with reject_code; reject_message}, CRt) + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = params; + balance = sysenv.balance; + context = context; + } + try + if fun > |table| then Trap + let func = table[fun] + if typeof(func) ≠ func (I) -> () then Trap + func(env) + discard_pending_call() + Return { + new_state = es.wasm_state; + new_calls = es.calls; + response = es.response; + cycles_used = es.cycles_used; + } + with Trap + if callbacks.on_cleanup = NoClosure then Trap {cycles_used = es.cycles_used;} + if callbacks.on_cleanup.fun > |table| then Trap {cycles_used = es.cycles_used;} + let func = table[callbacks.on_cleanup.fun] + if typeof(func) ≠ func (I) -> () then Trap {cycles_used = es.cycles_used;} + + let es' = ref { empty_execution_state with + wasm_state = wasm_state; + params = params; + balance = sysenv.balance - es.cycles_used; + context = CC; + } + try func(callbacks.on_cleanup.env) with Trap then Trap {cycles_used = es.cycles_used + es'.cycles_used;} + Return { + new_state = es'.wasm_state; + new_calls = []; + response = NoResponse; + cycles_used = es.cycles_used + es'.cycles_used; + } + ``` + + Note that if the initial callback handler traps, the cleanup callback (if present) is executed. + +- The `inspect_message` field of the `CanisterModule` is defined as follows. + + If the WebAssembly module does not export a function called under the name `canister_inspect_message`, then access is always granted: + ``` + inspect_message = λ (method_name, wasm_state, arg, caller, caller_info_data, caller_info_signer, sysenv) → + Return {status = Accept;} + ``` + + Otherwise, if the WebAssembly module exports a function `func` under the name `canister_inspect_message`, it is + ``` + inspect_message = λ (method_name, wasm_state, arg, caller, caller_info_data, caller_info_signer, sysenv) → + let es = ref {empty_execution_state with + wasm_state = wasm_state; + params = empty_params with { + arg = arg; + caller = caller; + caller_info_data = caller_info_data; + caller_info_signer = caller_info_signer; + method_name = method_name; + sysenv + } + balance = sysenv.balance; + cycles_available = 0; // ingress requests have no funds + context = F; + } + try func() with Trap then Trap + Return {status = es.ingress_filter;}; + ``` + +#### Helper functions + +In the following section, we use the these helper functions +``` +I ∈ {i32, i64} +copy_to_canister(dst : I, offset : I, size : I, data : blob) = + if offset+size > |data| then Trap {cycles_used = es.cycles_used;} + if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} + es.wasm_state.wasm_memory[dst..dst+size] := data[offset..offset+size] + +I ∈ {i32, i64} +copy_from_canister(src : I, size : I) blob = + if src+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} + return es.wasm_state.wasm_memory[src..src+size] +``` + +Cycles are represented by 128-bit values so they require 16 bytes of memory. +``` +I ∈ {i32, i64} +copy_cycles_to_canister(dst : I, data : blob) = + let size = 16; + if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} + es.wasm_state.wasm_memory[dst..dst+size] := data[0..size] +``` + +Helper function to get sorted keys from environment variables map. +``` +get_sorted_env_keys(env_vars : (text -> text)) = + let keys = [] + for (key, _) in env_vars: + keys := keys · [key] + return sort_lexicographically(keys) +``` + +#### System imports + +Upon *instantiation* of the WebAssembly module, we can provide the following functions as imports. + +The pseudo-code below does *not* explicitly enforce the restrictions of which imports are available in which contexts; for that the table in [Overview of imports](./canister-interface.md#system-api-imports) is authoritative, and is assumed to be part of the implementation. +``` +I ∈ {i32, i64} +ic0.msg_arg_data_size() : I = + if es.context ∉ {I, U, RQ, NRQ, TQ, CQ, Ry, CRy, F} then Trap {cycles_used = es.cycles_used;} + return |es.params.arg| + +I ∈ {i32, i64} +ic0.msg_arg_data_copy(dst : I, offset : I, size : I) = + if es.context ∉ {I, U, RQ, NRQ, TQ, CQ, Ry, CRy, F} then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.arg) + +I ∈ {i32, i64} +ic0.msg_caller_size() : I = + if es.context = s then Trap {cycles_used = es.cycles_used;} + return |es.params.caller| + +I ∈ {i32, i64} +ic0.msg_caller_copy(dst : I, offset : I, size : I) = + if es.context = s then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.caller) + +I ∈ {i32, i64} +ic0.msg_caller_info_data_size() : I = + if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} + return |es.params.caller_info_data| + +I ∈ {i32, i64} +ic0.msg_caller_info_data_copy(dst : I, offset : I, size : I) = + if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.caller_info_data) + +I ∈ {i32, i64} +ic0.msg_caller_info_signer_size() : I = + if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} + return |es.params.caller_info_signer| + +I ∈ {i32, i64} +ic0.msg_caller_info_signer_copy(dst : I, offset : I, size : I) = + if es.context ∉ {U, RQ, NRQ, CQ, Ry, Rt, CRy, CRt, C, CC, F} then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.caller_info_signer) + +ic0.msg_reject_code() : i32 = + if es.context ∉ {Ry, Rt, CRy, CRt, C} then Trap {cycles_used = es.cycles_used;} + es.params.reject_code + +I ∈ {i32, i64} +ic0.msg_reject_msg_size() : I = + if es.context ∉ {Rt, CRt} then Trap {cycles_used = es.cycles_used;} + return |es.params.reject_msg| + +I ∈ {i32, i64} +ic0.msg_reject_msg_copy(dst : I, offset : I, size : I) = + if es.context ∉ {Rt, CRt} then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.reject_msg) + +ic0.msg_deadline() : i64 = + if es.context ∉ {U, Q, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} + if es.params.deadline = Timestamp t + then return t + else return 0 + +I ∈ {i32, i64} +ic0.msg_reply_data_append(src : I, size : I) = + if es.context ∉ {U, RQ, NRQ, TQ, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} + if es.response ≠ NoResponse then Trap {cycles_used = es.cycles_used;} + es.reply_params.arg := es.reply_params.arg · copy_from_canister(src, size) + +ic0.msg_reply() = + if es.context ∉ {U, RQ, NRQ, TQ, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} + if es.response ≠ NoResponse then Trap {cycles_used = es.cycles_used;} + es.response := Reply (es.reply_params.arg) + es.cycles_available := 0 + +I ∈ {i32, i64} +ic0.msg_reject(src : I, size : I) = + if es.context ∉ {U, RQ, NRQ, TQ, CQ, Ry, Rt, CRy, CRt} then Trap {cycles_used = es.cycles_used;} + if es.response ≠ NoResponse then Trap {cycles_used = es.cycles_used;} + es.response := Reject (CANISTER_REJECT, copy_from_canister(src, size)) + es.cycles_available := 0 + +ic0.msg_cycles_available() : i64 = + if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} + if es.cycles_available >= 2^64 then Trap {cycles_used = es.cycles_used;} + return es.cycles_available + +I ∈ {i32, i64} +ic0.msg_cycles_available128(dst : I) = + if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} + let amount = es.cycles_available + copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) + +ic0.msg_cycles_refunded() : i64 = + if es.context ∉ {Rt, Ry} then Trap {cycles_used = es.cycles_used;} + if es.params.cycles_refunded >= 2^64 then Trap {cycles_used = es.cycles_used;} + return es.params.cycles_refunded + +I ∈ {i32, i64} +ic0.msg_cycles_refunded128(dst : I) = + if es.context ∉ {Rt, Ry} then Trap {cycles_used = es.cycles_used;} + let amount = es.params.cycles_refunded + copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) + +ic0.msg_cycles_accept(max_amount : i64) : i64 = + if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} + let amount = min(max_amount, es.cycles_available) + es.cycles_available := es.cycles_available - amount + es.cycles_accepted := es.cycles_accepted + amount + es.balance := es.balance + amount + return amount + +I ∈ {i32, i64} +ic0.msg_cycles_accept128(max_amount_high : i64, max_amount_low : i64, dst : I) = + if es.context ∉ {U, RQ, Rt, Ry} then Trap {cycles_used = es.cycles_used;} + let max_amount = max_amount_high * 2^64 + max_amount_low + let amount = min(max_amount, es.cycles_available) + es.cycles_available := es.cycles_available - amount + es.cycles_accepted := es.cycles_accepted + amount + es.balance := es.balance + amount + copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) + +I ∈ {i32, i64} +ic0.cycles_burn128(amount_high : i64, amount_low : i64, dst : I) = + if es.context ∉ {I, G, U, RQ, 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)) + es.balance := es.balance - burned_amount + copy_cycles_to_canister(dst, burned_amount.to_little_endian_bytes()) + +I ∈ {i32, i64} +ic0.canister_self_size() : I = + if es.context = s then Trap {cycles_used = es.cycles_used;} + return |es.wasm_state.self_id| + +I ∈ {i32, i64} +ic0.canister_self_copy(dst : I, offset : I, size : I) = + if es.context = s then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.wasm_state.self_id) + +I ∈ {i32, i64} +ic0.subnet_self_size() : I = + if es.context = s then Trap {cycles_used = es.cycles_used;} + return |es.params.sysenv.subnet_id| + +I ∈ {i32, i64} +ic0.subnet_self_copy(dst : I, offset : I, size : I) = + if es.context = s then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.sysenv.subnet_id) + +ic0.canister_cycle_balance() : i64 = + if es.context = s then Trap {cycles_used = es.cycles_used;} + if es.balance >= 2^64 then Trap {cycles_used = es.cycles_used;} + return es.balance + +I ∈ {i32, i64} +ic0.canister_cycle_balance128(dst : I) = + if es.context = s then Trap {cycles_used = es.cycles_used;} + let amount = es.balance + copy_cycles_to_canister(dst, amount.to_little_endian_bytes()) + +I ∈ {i32, i64} +ic0.canister_liquid_cycle_balance128(dst : I) = + if es.context = s then Trap {cycles_used = es.cycles_used;} + copy_cycles_to_canister(dst, liquid_balance(es).to_little_endian_bytes()) + +ic0.canister_status() : i32 = + if es.context = s then Trap {cycles_used = es.cycles_used;} + match es.params.sysenv.canister_status with + Running -> return 1 + Stopping -> return 2 + Stopped -> return 3 + +ic0.canister_version() : i64 = + if es.context = s then Trap {cycles_used = es.cycles_used;} + return es.params.sysenv.canister_version + +I ∈ {i32, i64} +ic0.msg_method_name_size() : I = + if es.context ∉ {F} then Trap {cycles_used = es.cycles_used;} + return |es.method_name| + +I ∈ {i32, i64} +ic0.msg_method_name_copy(dst : I, offset : I, size : I) = + if es.context ∉ {F} then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.method_name) + +ic0.accept_message() = + if es.context ∉ {F} then Trap {cycles_used = es.cycles_used;} + if es.ingress_filter = Accept then Trap {cycles_used = es.cycles_used;} + es.ingress_filter = Accept + +I ∈ {i32, i64} +ic0.call_new( + callee_src : I, + callee_size : I, + name_src : I, + name_size : I, + reply_fun : I, + reply_env : I, + reject_fun : I, + reject_env : I, + ) = + if es.context ∉ {U, CQ, Ry, Rt, CRy, CRt, T} then Trap {cycles_used = es.cycles_used;} + + discard_pending_call() + + callee := copy_from_canister(callee_src, callee_size); + method_name := copy_from_canister(name_src, name_size); + + es.pending_call = MethodCall { + callee = callee; + method_name = callee; + arg = ""; + transferred_cycles = 0; + callback = Callback { + on_reply = Closure { fun = reply_fun; env = reply_env } + on_reject = Closure { fun = reject_fun; env = reject_env } + on_cleanup = NoClosure + }; + } + +ic0.call_with_best_effort_response(timeout_seconds : i32) = + if + es.context ∉ {U, CQ, Ry, Rt, CRy, CRt, T} + or es.pending_call = NoPendingCall + or es.pending_call.timeout_seconds ≠ NoTimeout + then Trap {cycles_used = es.cycles_used;} + es.pending_call.timeout_seconds := min(timeout_seconds, MAX_CALL_TIMEOUT) + +I ∈ {i32, i64} +ic0.call_on_cleanup (fun : I, env : I) = + 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;} + if es.pending_call.callback.on_cleanup ≠ NoClosure then Trap {cycles_used = es.cycles_used;} + es.pending_call.callback.on_cleanup := Closure { fun = fun; env = env} + +I ∈ {i32, i64} +ic0.call_data_append (src : I, size : I) = + 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.pending_call.arg := es.pending_call.arg · copy_from_canister(src, size) + +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) < 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) < 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_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;} + + // `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 + +// helper function +discard_pending_call() = + if es.pending_call ≠ NoPendingCall then + es.balance := es.balance + es.pending_call.transferred_cycles + es.pending_call := NoPendingCall + +ic0.stable_size() : (page_count : i32) = + if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} + page_count := |es.wasm_state.stable_memory| / 64k + return page_count + +ic0.stable_grow(new_pages : i32) : (old_page_count : i32) = + if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} + if arbitrary() then return -1 + else + old_size := |es.wasm_state.stable_memory| / 64k + if old_size + new_pages > 2^16 then return -1 + es.wasm_state.stable_memory := + es.wasm_state.stable_memory · repeat(0x00, new_pages * 64k) + return old_size + +ic0.stable_write(offset : i32, src : i32, size : i32) + if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} + if src+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} + if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} + + es.wasm_state.stable_memory[offset..offset+size] := es.wasm_state.wasm_memory[src..src+size] + +ic0.stable_read(dst : i32, offset : i32, size : i32) + if |es.wasm_state.wasm_memory| > 2^32 then Trap {cycles_used = es.cycles_used;} + if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} + if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} + + es.wasm_state.wasm_memory[offset..offset+size] := es.wasm_state.stable_memory[src..src+size] + +ic0.stable64_size() : (page_count : i64) = + return |es.wasm_state.stable_memory| / 64k + +ic0.stable64_grow(new_pages : i64) : (old_page_count : i64) = + if arbitrary() + then return -1 + else + old_size := |es.wasm_state.stable_memory| / 64k + es.wasm_state.stable_memory := + es.wasm_state.stable_memory · repeat(0x00, new_pages * 64k) + return old_size + +ic0.stable64_write(offset : i64, src : i64, size : i64) + if src+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} + if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} + + es.wasm_state.stable_memory[offset..offset+size] := es.wasm_state.wasm_memory[src..src+size] + +ic0.stable64_read(dst : i64, offset : i64, size : i64) + if offset+size > |es.wasm_state.stable_memory| then Trap {cycles_used = es.cycles_used;} + if dst+size > |es.wasm_state.wasm_memory| then Trap {cycles_used = es.cycles_used;} + + es.wasm_state.wasm_memory[offset..offset+size] := es.wasm_state.stable_memory[src..src+size] + +I ∈ {i32, i64} +ic0.root_key_size() : I = + if es.context ∉ {I, G, U, RQ, Ry, Rt, C, T} then Trap {cycles_used = es.cycles_used;} + let root_key = + return |root_key| + +I ∈ {i32, i64} +ic0.root_key_copy(dst : I, offset : I, size : I) = + if es.context ∉ {I, G, U, RQ, Ry, Rt, C, T} then Trap {cycles_used = es.cycles_used;} + let root_key = + copy_to_canister(dst, offset, size, root_key) + +I ∈ {i32, i64} +ic0.certified_data_set(src : I, size : I) = + if es.context ∉ {I, G, U, Ry, Rt, T} then Trap {cycles_used = es.cycles_used;} + es.new_certified_data := es.wasm_state[src..src+size] + +ic0.data_certificate_present() : i32 = + if es.context = s then Trap {cycles_used = es.cycles_used;} + if es.params.sysenv.certificate = NoCertificate + then return 0 + else return 1 + +I ∈ {i32, i64} +ic0.data_certificate_size() : I = + if es.context ∉ {NRQ, CQ} then Trap {cycles_used = es.cycles_used;} + if es.params.sysenv.certificate = NoCertificate then Trap {cycles_used = es.cycles_used;} + return |es.params.sysenv.certificate| + +I ∈ {i32, i64} +ic0.data_certificate_copy(dst : I, offset : I, size : I) = + if es.context ∉ {NRQ, CQ} then Trap {cycles_used = es.cycles_used;} + if es.params.sysenv.certificate = NoCertificate then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, es.params.sysenv.certificate) + +ic0.time() : i64 = + if es.context = s then Trap {cycles_used = es.cycles_used;} + return es.params.sysenv.time + +ic0.global_timer_set(timestamp: i64) : i64 = + if es.context ∉ {I, G, U, Ry, Rt, C, T} then Trap {cycles_used = es.cycles_used;} + let prev_global_timer = es.new_global_timer + es.new_global_timer := timestamp + if prev_global_timer = NoGlobalTimer + then return es.params.sysenv.global_timer + else return prev_global_timer + +ic0.performance_counter(counter_type : i32) : i64 = + arbitrary() + +I ∈ {i32, i64} +ic0.is_controller(src : I, size : I) : (result: i32) = + bytes = copy_from_canister(src, size) + if bytes encode a principal then + if bytes ∉ es.params.sysenv.controllers + then return 0 + else return 1 + else + Trap {cycles_used = es.cycles_used;} + +ic0.in_replicated_execution() : i32 = + if es.context ∈ {I, G, U, RQ, Ry, Rt, C, T, s} + then return 1 + else return 0 + +I ∈ {i32, i64} +ic0.cost_call(method_name_size: i64, payload_size: i64, dst: I) : () = + copy_cycles_to_canister(dst, arbitrary()) + +I ∈ {i32, i64} +ic0.cost_create_canister(dst: I) : () = + copy_cycles_to_canister(dst, arbitrary()) + +I ∈ {i32, i64} +ic0.cost_http_request(request_size: i64, max_res_bytes: i64, dst: I) : () = + copy_cycles_to_canister(dst, arbitrary()) + +I ∈ {i32, i64} +ic0.cost_sign_with_ecdsa(src: I, size: I, ecdsa_curve: i32, dst: I) : i32 = + known_keys = arbitrary() + known_curves = arbitrary() + key_name = copy_from_canister(src, size) + if ecdsa_curve ∉ known_curves then + return 1 + if key_name ∉ known_keys then + return 2 + copy_cycles_to_canister(dst, arbitrary()) + return 0 + +I ∈ {i32, i64} +ic0.cost_sign_with_schnorr(src: I, size: I, algorithm: i32, dst: I) : i32 = + known_keys = arbitrary() + known_algorithms = arbitrary() + key_name = copy_from_canister(src, size) + if algorithm ∉ known_algorithms then + return 1 + if key_name ∉ known_keys then + return 2 + copy_cycles_to_canister(dst, arbitrary()) + return 0 + +I ∈ {i32, i64} +ic0.cost_vetkd_derive_key(src: I, size: I, vetkd_curve: i32, dst: I) : i32 = + known_keys = arbitrary() + known_curves = arbitrary() + key_name = copy_from_canister(src, size) + if vetkd_curve ∉ known_curves then + return 1 + if key_name ∉ known_keys then + return 2 + copy_cycles_to_canister(dst, arbitrary()) + return 0 + +I ∈ {i32, i64} +ic0.env_var_count() : I = + if es.context = s then Trap {cycles_used = es.cycles_used;} + return |es.params.sysenv.environment_variables| + +I ∈ {i32, i64} +ic0.env_var_name_size(index : I) : I = + if es.context = s then Trap {cycles_used = es.cycles_used;} + if index >= |es.params.sysenv.environment_variables| then Trap {cycles_used = es.cycles_used;} + let sorted_keys = get_sorted_env_keys(es.params.sysenv.environment_variables) + return |sorted_keys[index]| + +I ∈ {i32, i64} +ic0.env_var_name_copy(index : I, dst : I, offset : I, size : I) = + if es.context = s then Trap {cycles_used = es.cycles_used;} + if index >= |es.params.sysenv.environment_variables| then Trap {cycles_used = es.cycles_used;} + let sorted_keys = get_sorted_env_keys(es.params.sysenv.environment_variables) + let name_var = sorted_keys[index] + copy_to_canister(dst, offset, size, name_var) + +I ∈ {i32, i64} +ic0.env_var_name_exists(name_src : I, name_size : I) : i32 = + if es.context = s then Trap {cycles_used = es.cycles_used;} + if name_size > MAX_ENV_VAR_NAME_LENGTH then Trap {cycles_used = es.cycles_used;} + let name_var = copy_from_canister(name_src, name_size) + if !is_valid_utf8(name_var) then Trap {cycles_used = es.cycles_used;} + if value_var ∈ dom(es.params.sysenv.environment_variables) then + return 1 + else + return 0 + +I ∈ {i32, i64} +ic0.env_var_value_size(name_src : I, name_size : I) : I = + if es.context = s then Trap {cycles_used = es.cycles_used;} + if name_size > MAX_ENV_VAR_NAME_LENGTH then Trap {cycles_used = es.cycles_used;} + let name_var = copy_from_canister(name_src, name_size) + if !is_valid_utf8(name_var) then Trap {cycles_used = es.cycles_used;} + let value_var = es.params.sysenv.environment_variables[name_var] + if value_var = null then Trap {cycles_used = es.cycles_used;} + return |value_var| + +I ∈ {i32, i64} +ic0.env_var_value_copy(name_src : I, name_size : I, dst : I, offset : I, size : I) = + if es.context = s then Trap {cycles_used = es.cycles_used;} + if name_size > MAX_ENV_VAR_NAME_LENGTH then Trap {cycles_used = es.cycles_used;} + let name_var = copy_from_canister(name_src, name_size) + if !is_valid_utf8(name_var) then Trap {cycles_used = es.cycles_used;} + let value_var = es.params.sysenv.environment_variables[name_var] + if value_var = null then Trap {cycles_used = es.cycles_used;} + copy_to_canister(dst, offset, size, value_var) + +I ∈ {i32, i64} +ic0.debug_print(src : I, size : I) = + return + +I ∈ {i32, i64} +ic0.trap(src : I, size : I) = + Trap {cycles_used = es.cycles_used;} +``` + + diff --git a/docs/references/ic-interface-spec/canister-interface.md b/docs/references/ic-interface-spec/canister-interface.md new file mode 100644 index 0000000..c7f5d8a --- /dev/null +++ b/docs/references/ic-interface-spec/canister-interface.md @@ -0,0 +1,1035 @@ +--- +title: "Canister Interface (System API)" +description: "WebAssembly module format and the System API available to canisters at runtime" +sidebar: + label: "Canister Interface" + order: 2 +--- + +## Canister module format {#canister-module-format} + +A canister module is a [WebAssembly module](https://webassembly.github.io/spec/core/index.html) that is either in binary format (typically `.wasm`) or gzip-compressed (typically `.wasm.gz`). If the module starts with byte sequence `[0x1f, 0x8b, 0x08]`, then the system decompresses the contents as a gzip stream according to [RFC-1952](https://datatracker.ietf.org/doc/html/rfc1952.html) and then parses the output as a WebAssembly binary. + +## Canister interface (System API) {#system-api} + +The System API is the interface between the running canister and the Internet Computer. It allows the WebAssembly module of a canister to expose functionality to the users (method entry points) and the IC (e.g. initialization), and exposes functionality of the IC to the canister (e.g. calling other canisters). Because WebAssembly is rather low-level, it also explains how to express higher level concepts (e.g. binary blobs). + +We want to leverage advanced WebAssembly features, such as WebAssembly host references. But as they are not yet supported by all tools involved, this section describes an initial System API that does not rely on host references. In section [Outlook: Using Host References](#host-references), we outline some of the proposed uses of WebAssembly host references. + +### WebAssembly module requirements {#system-api-module} + +In order for a WebAssembly module to be usable as the code for the canister, it needs to conform to the following requirements: + +- It may declare (import or export) at most one memory. + +- It may only import a function if it is listed in [Overview of imports](#system-api-imports). + In particular, all imported functions belong to the `ic0` module (denoted by the prefix `ic0.`). + The value of `I ∈ {i32, i64}` specifying whether the imported functions have 32-bit or 64-bit pointers + is derived from the bit-width of the declared memory defaulting to `I = i32` if the canister declares no memory. + +- It may have a `(start)` function. + +- If it exports a function called `canister_init`, the function must have type `() -> ()`. + +- If it exports a function called `canister_inspect_message`, the function must have type `() -> ()`. + +- If it exports a function called `canister_pre_upgrade`, the function must have type `() -> ()`. + +- If it exports a function called `canister_post_upgrade`, the function must have type `() -> ()`. + +- If it exports a function called `canister_heartbeat`, the function must have type `() -> ()`. + +- If it exports a function called `canister_on_low_wasm_memory`, the function must have type `() -> ()`. + +- If it exports a function called `canister_global_timer`, the function must have type `() -> ()`. + +- If it exports any functions called `canister_update `, `canister_query `, or `canister_composite_query ` for some `name`, the functions must have type `() -> ()`. + +- It may not export more than one function called `canister_update `, `canister_query `, or `canister_composite_query ` with the same `name`. + +- It may not export other methods the names of which start with the prefix `canister_` besides the methods allowed above. + +- It may not have both `icp:public ` and `icp:private ` with the same `name` as the custom section name. + +- It may not have other custom sections the names of which start with the prefix `icp:` besides the `icp:public ` and `icp:private `. + +- The IC may reject WebAssembly modules that + + - declare more than 50,000 functions, or + + - declare more than 1,000 globals, or + + - declare more than 16 exported custom sections (the custom section names with prefix `icp:`), or + + - the number of all exported functions called `canister_update `, `canister_query `, or `canister_composite_query ` exceeds 1,000, or + + - the sum of `` lengths in all exported functions called `canister_update `, `canister_query `, or `canister_composite_query ` exceeds 20,000, or + + - the total size of the custom sections (the sum of `` lengths in their names `icp:public ` and `icp:private ` plus the sum of their content lengths) exceeds 1MiB. + +### Interpretation of numbers + +WebAssembly number types (`i32`, `i64`) do not indicate if the numbers are to be interpreted as signed or unsigned. Unless noted otherwise, whenever the System API interprets them as numbers (e.g. memory pointers, buffer offsets, array sizes), they are to be interpreted as unsigned. + +### Entry points {#entry-points} + +The canister provides entry points which are invoked by the IC under various circumstances: + +- The canister may export a function with name `canister_init` and type `() -> ()`. + +- The canister may export a function with name `canister_pre_upgrade` and type `() -> ()`. + +- The canister may export a function with name `canister_post_upgrade` and type `() -> ()`. + +- The canister may export functions with name `canister_inspect_message` with type `() -> ()`. + +- The canister may export a function with name `canister_heartbeat` with type `() -> ()`. + +- The canister may export a function with name `canister_global_timer` with type `() -> ()`. + +- The canister may export functions with name `canister_update ` and type `() -> ()`. + +- The canister may export functions with name `canister_query ` and type `() -> ()`. + +- The canister may export functions with name `canister_composite_query ` and type `() -> ()`. + +- The canister may export a function with the name `canister_on_low_wasm_memory` and type `() -> ()`. + +- The canister table may contain functions of type `(env : I) -> ()` which may be used as callbacks for inter-canister calls and composite query methods. + The value of `I ∈ {i32, i64}` specifying whether the imported functions have 32-bit or 64-bit pointers + is derived from the bit-width of the declared memory defaulting to `I = i32` if the canister declares no memory. + +If the execution of any of these entry points traps for any reason, then all changes to the WebAssembly state, as well as the effect of any externally visible system call (like `ic0.msg_reply`, `ic0.msg_reject`, `ic0.call_perform`), are discarded. For upgrades, this transactional behavior applies to the `canister_pre_upgrade`/`canister_post_upgrade` sequence as a whole. + +#### Canister initialization {#system-api-init} + +If `canister_init` is present, then this is the first exported WebAssembly function invoked by the IC. The argument that was passed along with the canister initialization call (see [IC method](./management-canister.md#ic-install_code)) is available to the canister via `ic0.msg_arg_data_size/copy`. + +The IC assumes the canister to be fully instantiated if the `canister_init` method entry point returns. If the `canister_init` method entry point traps, then canister installation has failed, and the canister is reverted to its previous state (i.e. empty with `install`, or whatever it was for a `reinstall`). + +#### Canister upgrades {#system-api-upgrades} + +When a canister is upgraded to a new WebAssembly module, the IC: + +1. Invokes `canister_pre_upgrade` (if exported by the current canister code and `skip_pre_upgrade` is not `opt true`) on the old instance, to give the canister a chance to clean up (e.g. move data to [stable memory](#system-api-stable-memory)). + +2. Instantiates the new module, including the execution of `(start)`, with a fresh WebAssembly state. + +3. Invokes `canister_post_upgrade` (if present) on the new instance, passing the `arg` provided in the `install_code` call ([IC method](./management-canister.md#ic-install_code)). + +The stable memory is preserved throughout the process; the WebAssembly memory is discarded unless `wasm_memory_persistence` is `opt keep`; any other WebAssembly state is discarded. + +During these steps, no other entry point of the old or new canister is invoked. The `canister_init` function of the new canister is *not* invoked. + +These steps are atomic: If `canister_pre_upgrade` or `canister_post_upgrade` trap, the upgrade has failed, and the canister is reverted to the previous state. Otherwise, the upgrade has succeeded, and the old instance is discarded. + +:::note +The `skip_pre_upgrade` flag can be enabled to skip the execution of the `canister_pre_upgrade` method on the old canister instance. +The main purpose of this mode is recovery from cases when the `canister_pre_upgrade` hook traps unconditionally preventing the normal upgrade path. + +Skipping the pre-upgrade can lead to data loss. +Use it only as the last resort and only if the stable memory already contains the entire canister state. +::: + +#### Public methods {#system-api-requests} + +To define a public method of name `name`, a WebAssembly module exports a function with name `canister_update `, `canister_query `, or `canister_composite_query ` and type `() -> ()`. We call this the *method entry point*. The name of the exported function distinguishes update, query, and composite query methods. + +:::note + +The space in `canister_update `, `canister_query `, and `canister_composite_query `, resp., is intentional. There is exactly one space between `canister_update/canister_query/canister_composite_query` and the ``. + +::: + +The argument of the call (e.g. the content of the `arg` field in the [API request to call a canister method](./https-interface.md#http-call)) is copied into the canister on demand using the System API functions shown below. + +Eventually, a method will want to send a response, using `ic0.reply` or `ic0.reject` + +#### Heartbeat + +For periodic or time-based execution, the WebAssembly module can export a function with name `canister_heartbeat`. The heartbeats scheduling algorithm is implementation-defined. + +`canister_heartbeat` is triggered by the IC, and therefore has no arguments and cannot reply or reject. Still, the function `canister_heartbeat` can initiate new calls. + +:::note + +While an implementation will likely try to keep the interval between `canister_heartbeat` invocations to within a few seconds, this is not formally part of this specification. + +::: + +#### Global timer {#global-timer} + +For time-based execution, the WebAssembly module can export a function with name `canister_global_timer`. This function is called if the canister has set its global timer (using the System API function `ic0.global_timer_set`) and the current time (as returned by the System API function `ic0.time`) has passed the value of the global timer. + +Once the function `canister_global_timer` is scheduled, the canister's global timer is deactivated. The global timer is also deactivated upon changes to the canister's Wasm module (calling `install_code`, `install_chunked_code`, `uninstall_code` methods of the management canister or if the canister runs out of cycles). In particular, the function `canister_global_timer` won't be scheduled again unless the canister sets the global timer again (using the System API function `ic0.global_timer_set`). The global timer scheduling algorithm is implementation-defined. + +`canister_global_timer` is triggered by the IC, and therefore has no arguments and cannot reply or reject. Still, the function `canister_global_timer` can initiate new calls. + +:::note + +While an implementation will likely try to keep the interval between the value of the global timer and the time-stamp of the `canister_global_timer` invocation within a few seconds, this is not formally part of this specification. + +::: + +#### On Low Wasm Memory {#on-low-wasm-memory} + +A canister can export a function with the name `canister_on_low_wasm_memory`, which is scheduled whenever the canister's remaining wasm memory size in bytes falls from at least a threshold `t` to strictly less than `t`. +The threshold `t` can be defined in the field `wasm_memory_threshold` in the [canister's settings](./management-canister.md#ic-update_settings) and by default it is set to 0. + +:::note + +While the above function is scheduled immediately once the condition above is triggered, it may not necessarily be executed immediately if the canister does not have enough cycles. +If the canister gets frozen immediately after the function is scheduled for execution, the function will run once the canister's unfrozen _if_ the canister's remaining wasm memory size in bytes remains strictly less than the threshold `t`. +::: + +#### Callbacks + +Callbacks are addressed by their table index (as a proxy for a Wasm `funcref`). + +In the reply callback of a [inter-canister method call](#system-api-call), the argument refers to the response to that call. In reject callbacks, no argument is available. + +### Replicated and Non-Replicated execution mode + +Canister methods can be executed either in *replicated* mode where the method runs on all subnet nodes and the results go through consensus or in *non-replicated* mode where the method runs on a single node and the result does not go through consensus. The trade-off between replicated and non-replicated mode is therefore one between the result's latency and trustworthiness. + +The following table captures the modes that different canister methods can be executed in. + +| Canister method | Replicated Mode | Non-Replicated Mode | +| --------------------------- | --------------- | ------------------- | +| canister_update | Yes | No | +| canister_query | Yes | Yes | +| canister_composite_query | No | Yes | +| canister_inspect_message | No | Yes | +| canister_init | Yes | No | +| canister_pre_upgrade | Yes | No | +| canister_post_upgrade | Yes | No | +| canister_heartbeat | Yes | No | +| canister_global_timer | Yes | No | +| canister_on_low_wasm_memory | Yes | No | + +### Overview of imports {#system-api-imports} + +:::note + +The 32-bit stable memory System API (`ic0.stable_size`, `ic0.stable_grow`, `ic0.stable_write`, and `ic0.stable_read`) is DEPRECATED. Canister developers are advised to use the 64-bit stable memory System API instead. + +::: + +The following sections describe various System API functions, also referred to as system calls, which we summarize here. + +All the following functions belong to the `ic0` module (denoted by the prefix `ic0.`). + +In the following, the value of `I ∈ {i32, i64}` specifies whether the imported functions have 32-bit or 64-bit pointers. +Given a canister module, the value of `I ∈ {i32, i64}` is derived from the bit-width of the declared memory +defaulting to `I = i32` if the canister declares no memory. + +``` + ic0.msg_arg_data_size : () -> I; // I U RQ NRQ TQ CQ Ry CRy F + ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) -> (); // I U RQ NRQ TQ CQ Ry CRy F + ic0.msg_caller_size : () -> I; // * + ic0.msg_caller_copy : (dst : I, offset : I, size : I) -> (); // * + ic0.msg_caller_info_data_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F + ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F + ic0.msg_caller_info_signer_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F + ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F + ic0.msg_reject_code : () -> i32; // Ry Rt CRy CRt C + ic0.msg_reject_msg_size : () -> I ; // Rt CRt + ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) -> (); // Rt CRt + + ic0.msg_deadline : () -> i64; // U Q CQ Ry Rt CRy CRt + + ic0.msg_reply_data_append : (src : I, size : I) -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt + ic0.msg_reply : () -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt + ic0.msg_reject : (src : I, size : I) -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt + + ic0.msg_cycles_available128 : (dst : I) -> (); // U RQ Rt Ry + ic0.msg_cycles_refunded128 : (dst : I) -> (); // Rt Ry + ic0.msg_cycles_accept128 : (max_amount_high : i64, max_amount_low: i64, dst : I) + -> (); // U RQ Rt Ry + + ic0.cycles_burn128 : (amount_high : i64, amount_low : i64, dst : I) + -> (); // I G U RQ Ry Rt C T + + ic0.canister_self_size : () -> I; // * + ic0.canister_self_copy : (dst : I, offset : I, size : I) -> (); // * + ic0.canister_cycle_balance128 : (dst : I) -> (); // * + ic0.canister_liquid_cycle_balance128 : (dst : I) -> (); // * + ic0.canister_status : () -> i32; // * + ic0.canister_version : () -> i64; // * + + ic0.subnet_self_size : () -> I; // * + ic0.subnet_self_copy : (dst : I, offset : I, size : I) -> (); // * + + ic0.msg_method_name_size : () -> I; // F + ic0.msg_method_name_copy : (dst : I, offset : I, size : I) -> (); // F + ic0.accept_message : () -> (); // F + + ic0.call_new : + ( callee_src : I, + callee_size : I, + name_src : I, + name_size : I, + reply_fun : I, + reply_env : I, + reject_fun : I, + reject_env : I + ) -> (); // U CQ Ry Rt CRy CRt T + ic0.call_on_cleanup : (fun : I, env : I) -> (); // U CQ Ry Rt CRy CRt T + ic0.call_data_append : (src : I, size : I) -> (); // U CQ Ry Rt CRy CRt T + ic0.call_with_best_effort_response : (timeout_seconds : i32) -> (); // U CQ Ry Rt CRy CRt T + ic0.call_cycles_add128 : (amount_high : i64, amount_low: i64) -> (); // U Ry Rt T + ic0.call_perform : () -> ( err_code : i32 ); // U CQ Ry Rt CRy CRt T + + ic0.stable64_size : () -> (page_count : i64); // * s + ic0.stable64_grow : (new_pages : i64) -> (old_page_count : i64); // * s + ic0.stable64_write : (offset : i64, src : i64, size : i64) -> (); // * s + ic0.stable64_read : (dst : i64, offset : i64, size : i64) -> (); // * s + + ic0.root_key_size : () -> I; // I G U RQ Ry Rt C T + ic0.root_key_copy : (dst : I, offset : I, size : I) -> (); // I G U RQ Ry Rt C T + ic0.certified_data_set : (src : I, size : I) -> (); // I G U Ry Rt T + ic0.data_certificate_present : () -> i32; // * + ic0.data_certificate_size : () -> I; // NRQ CQ + ic0.data_certificate_copy : (dst : I, offset : I, size : I) -> (); // NRQ CQ + + ic0.time : () -> (timestamp : i64); // * + ic0.global_timer_set : (timestamp : i64) -> i64; // I G U Ry Rt C T + ic0.performance_counter : (counter_type : i32) -> (counter : i64); // * s + ic0.is_controller : (src : I, size : I) -> ( result : i32); // * s + ic0.in_replicated_execution : () -> (result : i32); // * s + + ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> (); // * s + ic0.cost_create_canister : (dst : I) -> (); // * s + ic0.cost_http_request : (request_size : i64, max_res_bytes : i64, dst : I) -> (); // * s + ic0.cost_sign_with_ecdsa : (src : I, size : I, ecdsa_curve: i32, dst : I) -> i32; // * s + ic0.cost_sign_with_schnorr : (src : I, size : I, algorithm: i32, dst : I) -> i32; // * s + ic0.cost_vetkd_derive_key : (src : I, size : I, vetkd_curve: i32, dst : I) -> i32; // * s + + ic0.env_var_count : () -> I; // * + + ic0.env_var_name_size : (index: I) -> I; // * + ic0.env_var_name_copy : (index: I, dst: I, offset: I, size: I) -> (); // * + ic0.env_var_name_exists : (name_src: I, name_size: I) -> i32; // * + + ic0.env_var_value_size : (name_src: I, name_size: I) -> I; // * + ic0.env_var_value_copy : (name_src: I, name_size: I, dst: I, offset: I, size: I) -> (); // * + + ic0.debug_print : (src : I, size : I) -> (); // * s + ic0.trap : (src : I, size : I) -> (); // * s +``` + +The following System API functions are only available if `I = i32`, i.e., if the bit-width of the declared memory is 32 +or if the canister declares no memory. + +``` + ic0.msg_cycles_available : () -> i64; // U RQ Rt Ry + ic0.msg_cycles_refunded : () -> i64; // Rt Ry + ic0.msg_cycles_accept : (max_amount : i64) -> (amount : i64); // U RQ Rt Ry + ic0.canister_cycle_balance : () -> i64; // * + ic0.call_cycles_add : (amount : i64) -> (); // U Ry Rt T + ic0.stable_size : () -> (page_count : i32); // * s + ic0.stable_grow : (new_pages : i32) -> (old_page_count : i32); // * s + ic0.stable_write : (offset : i32, src : i32, size : i32) -> (); // * s + ic0.stable_read : (dst : i32, offset : i32, size : i32) -> (); // * s +``` + +The comment after each function lists from where these functions may be invoked: + +- `I`: from `canister_init` or `canister_post_upgrade` + +- `G`: from `canister_pre_upgrade` + +- `U`: from `canister_update …` + +- `RQ`: from `canister_query …` in replicated mode + +- `NRQ`: from `canister_query …` in non-replicated mode + +- `TQ`: from `canister_query …` in canister http outcall transform + +- `CQ`: from `canister_composite_query …` + +- `Ry`: from a reply callback + +- `Rt`: from a reject callback + +- `CRy`: from a reply callback in composite query + +- `CRt`: from a reject callback in composite query + +- `C`: from a cleanup callback + +- `CC`: from a cleanup callback in composite query + +- `s`: the `(start)` module initialization function + +- `F`: from `canister_inspect_message` + +- `T`: from *system task* (`canister_heartbeat` or `canister_global_timer` or `canister_on_low_wasm_memory`) + +- `*` = `I G U RQ NRQ TQ CQ Ry Rt CRy CRt C CC F T` (NB: Not `(start)`) + +If the canister invokes a system call from somewhere else, it will trap. + +Since Wasm doesn't have a 128-bit number type, calls requiring 128-bit arguments (e.g., the 128-bit versions of cycle operations) encode such arguments as a pair of 64-bit numbers containing the high and low bits. + +### Blob-typed arguments and results + +WebAssembly functions parameter and result types can only be primitive number types. To model functions that accept or return blobs or text values, the following idiom is used: + +To provide access to a string or blob `foo`, the System API provides two functions: +``` +ic0.foo_size : () -> I; I ∈ {i32, i64} +ic0.foo_copy : (dst : I, offset : I, size : I) -> (); I ∈ {i32, i64} +``` + +The `*_size` function indicates the size, in bytes, of `foo`. The `*_copy` function copies `size` bytes from `foo[offset..offset+size]` to `memory[dst..dst+size]`. This traps if `offset+size` is greater than the size of `foo`, or if `dst+size` exceeds the size of the Wasm memory. + +Dually, a System API function that conceptually takes a blob or string as a parameter `foo` has two parameters: + +``` +ic0.set_foo : (src : I, size : I) -> …; I ∈ {i32, i64} +``` + +which copies, at the time of function invocation, the data referred to by `src`/`size` out of the canister. Unless otherwise noted, this traps if `src+size` exceeds the size of the WebAssembly memory. + +### Method arguments + +The canister can access an argument. For `canister_init`, `canister_post_upgrade` and method entry points, the argument is the argument of the call; in a reply callback, it refers to the received reply. So the lifetime of the argument data is a single WebAssembly function execution, not the whole method call tree. + +- `ic0.msg_arg_data_size : () → I` and `ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + The message argument data. + +- `ic0.msg_caller_size : () → I` and `ic0.msg_caller_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + The identity of the caller, which may be a canister id or a user id. During canister installation or upgrade, this is the id of the user or canister requesting the installation or upgrade. During a system task (heartbeat or global timer), this is the id of the management canister. + +- `ic0.msg_caller_info_data_size : () → I`, `ic0.msg_caller_info_signer_size : () → I` and `ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) → ()`; and `ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + Auxiliary information about the caller as provided by the canister with which the caller's identity is associated (i.e., the public key of the canister signature is equal to the public key of the caller's identity). + These functions can only return non-empty values if the caller is a self-authenticating principal authenticated by canister signatures. In particular, they always return empty values when the caller is another canister. + + The `caller_info_data` may include information such as identity attributes of the caller. + The `_signer_` functions return the canister ID of the canister providing the signature, and the `_data_` functions return the data provided by the canister. + This auxiliary information can only be set if the caller principal is derived from the public key corresponding to a canister signature, and it is guaranteed to be properly signed by the issuing canister. + + These functions trap in `canister_init`, `canister_post_upgrade`, `canister_pre_upgrade`, canister http outcall transform, the `(start)` module initialization function, and system tasks (`canister_heartbeat` or `canister_global_timer` or `canister_on_low_wasm_memory`). + +- `ic0.msg_reject_code : () → i32` + + Returns the reject code, if the current function is invoked as a reject callback or as a cleanup callback of a reject callback. + + It returns the special "no error" code `0` if the callback is a reply callback or a cleanup callback of a reply callback; this allows canisters to use a single entry point for both the reply and reject callback, if they choose to do so. + +- `ic0.msg_reject_msg_size : () → I` and `ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + The reject message. Traps if there is no reject message (i.e. if `reject_code` is `0`). + +- `ic0.msg_deadline : () -> i64` + + The deadline, in nanoseconds since 1970-01-01, after which the caller might stop waiting for a response. + + For update methods and their callbacks, if the method was called via a bounded-wait inter-canister call the deadline is computed based on the time the call was made, and the `timeout_seconds` parameter provided by the caller. For other calls (including ingress messages and all calls to query and composite query methods, including calls in replicated mode) a deadline of 0 is returned. + +### Responding {#responding} + +Eventually, the canister will want to respond to the original call, either by replying (indicating success) or rejecting (signalling an error): + +- `ic0.msg_reply_data_append : (src : I, size : I) → ()`; `I ∈ {i32, i64}` + + Appends data it to the (initially empty) data reply. Traps if the total appended data exceeds the [maximum response size](../cycles-costs.md). + + This traps if the current call already has been or does not need to be responded to. + + Any data assembled, but not replied using `ic0.msg_reply`, gets discarded at the end of the current message execution. In particular, the reply buffer gets reset when the canister yields control without calling `ic0.msg_reply`. + +:::note + +This can be invoked multiple times within the same message execution to build up the argument with data from various places on the Wasm heap. This way, the canister does not have to first copy all the pieces from various places into one location. + +::: + +- `ic0.msg_reply : () → ()` + + Replies to the sender with the data assembled using `ic0.msg_reply_data_append`. + + This function can be called at most once (a second call will trap), and must be called exactly once to indicate success. + + See [Cycles](#system-api-cycles) for how this interacts with cycles available on this call. + +- `ic0.msg_reject : (src : I, size : I) → ()`; `I ∈ {i32, i64}` + + Rejects the call. The data referred to by `src`/`size` is used for the diagnostic message. + + This system call traps if `src+size` exceeds the size of the WebAssembly memory, or if the current call already has been or does not need to be responded to, or if the data referred to by `src`/`size` is not valid UTF8. + + The other end will receive this reject with reject code `CANISTER_REJECT`, see [Reject codes](./https-interface.md#reject-codes). + + Possible reply data assembled using `ic0.msg_reply_data_append` is discarded. + + See [Cycles](#system-api-cycles) for how this interacts with cycles available on this call. + +### Ingress message inspection {#system-api-inspect-message} + +A canister can inspect ingress messages before executing them. When the IC receives an update call from a user, the IC will use the canister method `canister_inspect_message` to determine whether the message shall be accepted. If the canister is empty (i.e. does not have a Wasm module), then the ingress message will be rejected. If the canister is not empty and does not implement `canister_inspect_message`, then the ingress message will be accepted. + +In `canister_inspect_message`, the canister can determine the name of the method called by the message using `ic0.msg_method_name_size : () → I` and `ic0.msg_method_name_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}`. + +In `canister_inspect_message`, the canister can accept the message by invoking `ic0.accept_message : () → ()`. This function traps if invoked twice. If the canister traps in `canister_inspect_message` or does not call `ic0.accept_message`, then the access is denied. + +:::note + +The `canister_inspect_message` is executed by a single node and thus its outcome depends on the state of this node. +In particular, the `canister_inspect_message` might be executed on a state that does not reflect the changes +made by a previously successfully completed update call if the `canister_inspect_message` is executed by a node +that is not up-to-date in terms of its state. + +::: + +:::note + +The `canister_inspect_message` is *not* invoked for query calls, inter-canister calls or calls to the management canister. + +::: + +### Self-identification {#system-api-canister-self} + +A canister can learn about its own identity: + +- `ic0.canister_self_size : () → I` and `ic0.canister_self_copy: (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + These functions allow the canister to query its own canister id (as a blob). + +A canister can learn about the subnet it is running on: + +- `ic0.subnet_self_size : () → I` and `ic0.subnet_self_copy: (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + These functions allow the canister to query the subnet id (as a blob) of the subnet on which the canister is running. + +### Canister status {#system-api-canister-status} + +This function allows a canister to find out if it is running, stopping or stopped (see [IC method](./management-canister.md#ic-canister_status) and [IC method](./management-canister.md#ic-stop_canister) for context). + +- `ic0.canister_status : () → i32` + + returns the current status of the canister: + + Status `1` indicates running, `2` indicates stopping, and `3` indicates stopped. + + Observing the canister status is particularly useful during `canister_post_upgrade`. Confirming that the status is 3 (stopped) helps prevent accidentally upgrading a canister that has not fully stopped. + +### 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 ID, canister's code, settings, running status (Running, Stopping, Stopped), cycles balance, and memory (WASM and stable memory), i.e., upon every successful canister renaming, 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, replicated query methods, response callbacks, heartbeats, and global timers. The system can also arbitrarily increment the canister version at any time. + +- `ic0.canister_version : () → i64` + + returns the current canister version. + +During the canister upgrade process, `canister_pre_upgrade` sees the old counter value, and `canister_post_upgrade` sees the new counter value. + +### Inter-canister method calls {#system-api-call} + +When handling an update call (or a callback), a canister can do further calls to another canister. Calls are assembled in a builder-like fashion, starting with `ic0.call_new`, adding more attributes using the `ic0.call_*` functions, and eventually performing the call with `ic0.call_perform`. + +- `ic0.call_new : + ( callee_src : I, + callee_size : I, + name_src : I, + name_size : I, + reply_fun : I, + reply_env : I, + reject_fun : I, + reject_env : I, + ) → ()`; `I ∈ {i32, i64}` + +Begins assembling a call to the canister specified by `callee_src/_size` at method `name_src/_size`. + +The IC records two mandatory callback functions, represented by a table entry index `*_fun` and some additional value `*_env`. When the response comes back, the table is read at the corresponding index, expected to be a function of type `(env : I) -> ()`, and passed the corresponding `*_env` value. + +The reply callback is executed upon successful completion of the method call, which can query the reply using `ic0.msg_arg_data_*`. + +The reject callback is executed if the method call fails asynchronously or the other canister explicitly rejects the call. The reject code and message can be queried using `ic0.msg_reject_code` and `ic0.msg_reject_msg_*`. + +Subsequent calls to the following functions set further attributes of that call, until the call is concluded (with `ic0.call_perform`) or discarded (by returning without calling `ic0.call_perform` or by starting a new call with `ic0.call_new`.) + +- `ic0.call_on_cleanup : (fun : I, env : I) → ()`; `I ∈ {i32, i64}` + +If a cleanup callback (of type `(env : I) -> ()`) is specified for this call, it is executed if and only if the `reply` or the `reject` callback was executed and trapped (for any reason). + +During the execution of the `cleanup` function, only a subset of the System API is available. The cleanup function is expected to run swiftly (within a fixed, yet to be specified cycle limit) and serves to free resources associated with the callback. + +If this traps (e.g. runs out of cycles), the state changes from the `cleanup` function are discarded, as usual, and no further actions are taken related to that call. Canisters likely want to avoid this from happening. + +There must be at most one call to `ic0.call_on_cleanup` between `ic0.call_new` and `ic0.call_perform`. + +- `ic0.call_with_best_effort_response : (timeout_seconds : i32) -> ()` + + Turns the call into a *bounded-wait call*, by relaxing the response delivery guarantee to be best effort, and asking the system to respond at the latest after `timeout_seconds` have elapsed. Best effort means the system may also respond with a `SYS_UNKNOWN` reject code, signifying that the call **may or may not** have been processed by the callee. Then, even if the callee produces a response, it will not be delivered to the caller. + + Any value for `timeout_seconds` is permitted, but is silently bounded from above by the `MAX_CALL_TIMEOUT` system constant; i.e., larger timeouts are treated as equivalent to `MAX_CALL_TIMEOUT` and do not cause an error. The implementation may add a specific [error code](./https-interface.md#error-codes) to a reject message to indicate the cause, in particular whether the timeout expired. Note that the reject callback may be executed (possibly significantly) later than the specified time (e.g., if the caller is under high load), or before timeout expiration (e.g., if the system is under load). + + A caller that receives a `SYS_UNKNOWN` reject code, yet needs to learn the call outcome, must find an out-of-band way of doing so. For example, if the callee provides idempotent function calls, the caller can simply retry the call. Sample causes of `SYS_UNKNOWN` include the call not being delivered in time, call processing not completing in time, reply delivery taking too long, and the system shedding load. + + This method can be called only in between `ic0.call_new` and `ic0.call_perform`, and at most once at that. Otherwise, it traps. A different timeout can be specified for each call. + +- `ic0.call_data_append : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` + + Appends the specified bytes to the argument of the call. Initially, the argument is empty. Traps if the total appended data exceeds the [maximum inter-canister call payload](../cycles-costs.md). + + This may be called multiple times between `ic0.call_new` and `ic0.call_perform`. + +- `ic0.call_cycles_add : (amount : i64) -> ()` + + This system call moves cycles from the canister balance onto the call under construction, to be transferred with that call. + + The cycles are deducted from the balance as shown by `ic0.canister_cycle_balance128` immediately, and moved back if the call cannot be performed (e.g. if `ic0.call_perform` signals an error, if the canister invokes `ic0.call_new`, or returns without calling `ic0.call_perform`). + + This system call may be called multiple times between `ic0.call_new` and `ic0.call_perform`. + + This system call traps if there is no call under construction, i.e., if not called between `ic0.call_new` and `ic0.call_perform`. + + This system call traps if trying to transfer more cycles than returned by `ic0.canister_liquid_cycle_balance128`. + +- `ic0.call_cycles_add128 : (amount_high : i64, amount_low : i64) -> ()` + + This system call moves cycles from the canister balance onto the call under construction, to be transferred with that call. + + The amount of cycles it moves is represented by a 128-bit value which can be obtained by combining the `amount_high` and `amount_low` parameters. + + The cycles are deducted from the balance as shown by `ic0.canister_cycle_balance128` immediately, and moved back if the call cannot be performed (e.g. if `ic0.call_perform` signals an error, if the canister invokes `ic0.call_new`, or returns without calling `ic0.call_perform`). + + This system call may be called multiple times between `ic0.call_new` and `ic0.call_perform`. + + This system call traps if there is no call under construction, i.e., if not called between `ic0.call_new` and `ic0.call_perform`. + + This system call traps if trying to transfer more cycles than returned by `ic0.canister_liquid_cycle_balance128`. + +- `ic0.call_perform : () -> ( err_code : i32 )` + + This concludes assembling the call. It queues the call message to the given destination, but does not actually act on it until the current WebAssembly function returns without trapping. + + This deducts `MAX_CYCLES_PER_RESPONSE` cycles from the canister balance and sets them aside for response processing. + + The returned `err_code` is always one of `0` and `2`. An `err_code` of `0` means that no error occurred and that the IC could 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. + + A non-zero value of `err_code` (`2`) indicates that the call could not be performed and the semantics of that value are the same as for the corresponding `SYS_FATAL` [reject code](./https-interface.md#reject-codes). The non-zero value of `err_code` (`2`) could arise due to a lack of resources within the IC, but also if the call would reduce the current cycle balance to a level below where the canister would be frozen. No callbacks are executed in this case. + + After `ic0.call_perform` and before the next call to `ic0.call_new`, all other `ic0.call_*` function calls trap. + +### Cycles {#system-api-cycles} + +Each canister maintains a balance of *cycles*, which are used to pay for platform usage. Cycles are represented by 128-bit values. + +:::note + +This specification currently does not go into details about which actions cost how many cycles and/or when. In general, you must assume that the canister's cycle balance can change arbitrarily between method executions, and during each System API function call, unless explicitly mentioned otherwise. + +::: + +- `ic0.canister_cycle_balance : () → i64` + + Indicates the current cycle balance of the canister. It is the canister balance before the execution of the current message, minus a reserve to pay for the execution of the current message and calls finalized via `ic0.call_perform`, minus any cycles queued up to be sent via `ic0.call_cycles_add` and `ic0.call_cycles_add128`. After execution of the message, the IC may add unused cycles from the reserve back to the balance. + +:::note + +This call traps if the current balance does not fit into a 64-bit value. Canisters that need to deal with larger cycles balances should use `ic0.canister_cycle_balance128` instead. + +::: + +- `ic0.canister_cycle_balance128 : (dst : I) → ()`; `I ∈ {i32, i64}` + + Indicates the current cycle balance of the canister by copying the value at the location `dst` in the canister memory. It is the canister balance before the execution of the current message, minus a reserve to pay for the execution of the current message and calls finalized via `ic0.call_perform`, minus any cycles queued up to be sent via `ic0.call_cycles_add` and `ic0.call_cycles_add128`. After execution of the message, the IC may add unused cycles from the reserve back to the balance. + +- `ic0.canister_liquid_cycle_balance128 : (dst : I) → ()`; `I ∈ {i32, i64}` + + Indicates the current amount of cycles that is available for spending in calls and execution by copying the value at the location `dst` in the canister memory. This amount of cycles can be safely attached to a call via `ic0.call_cycles_add128` as long as the memory usage of the canister does not increase for the rest of the current message execution. Hence, it is recommended to never attach the entire `ic0.canister_liquid_cycle_balance128` to a call, but leave some slack based on the expected canister memory usage and freezing threshold. + +- `ic0.msg_cycles_available : () → i64` + + Returns the amount of cycles that were transferred by the caller of the current call, and is still available in this message. + + Initially, in the update method entry point, this is the amount that the caller passed to the canister. When cycles are accepted (`ic0.msg_cycles_accept`), this reports fewer cycles accordingly. When the call is responded to (reply or reject), all available cycles are refunded to the caller, and this will return 0. + +:::note + +This call traps if the amount of cycles available does not fit into a 64-bit value. Please use `ic0.msg_cycles_available128` instead. + +::: + +- `ic0.msg_cycles_available128 : (dst : I) → ()`; `I ∈ {i32, i64}` + + Indicates the number of cycles transferred by the caller of the current call, still available in this message. The amount of cycles is represented by a 128-bit value. This call copies this value starting at the location `dst` in the canister memory. + + Initially, in the update method entry point, this is the amount that the caller passed to the canister. When cycles are accepted (`ic0.msg_cycles_accept128`), this reports fewer cycles accordingly. When the call is responded to (reply or reject), all available cycles are refunded to the caller, and this will report 0 cycles. + +- `ic0.msg_cycles_accept : (max_amount : i64) → (amount : i64)` + + This moves cycles from the call to the canister balance. It moves as many cycles as possible, up to these constraints: + + It moves no more cycles than `max_amount`. + + It moves no more cycles than available according to `ic0.msg_cycles_available`, and + + It can be called multiple times, each time possibly adding more cycles to the balance. + + The return value indicates how many cycles were actually moved. + + This system call does not trap. + +:::tip + +Example: To accept all cycles provided in a call, invoke `ic0.msg_cycles_accept(ic0.msg_cycles_available())` in the method handler or a callback handler, *before* calling reply or reject. + +::: + +- `ic0.msg_cycles_accept128 : (max_amount_high : i64, max_amount_low : i64, dst : I) → ()`; `I ∈ {i32, i64}` + + This moves cycles from the call to the canister balance. It moves as many cycles as possible, up to these constraints: + + It moves no more cycles than the amount obtained by combining `max_amount_high` and `max_amount_low`. Cycles are represented by 128-bit values. + + It moves no more cycles than available according to `ic0.msg_cycles_available128`, and + + It can be called multiple times, each time possibly adding more cycles to the balance. + + This call also copies the amount of cycles that were actually moved starting at the location `dst` in the canister memory. + + This does not trap. + +- `ic0.cycles_burn128 : (amount_high : i64, amount_low : i64, dst : I) -> ()`; `I ∈ {i32, i64}` + + This burns cycles from the canister. It burns as many cycles as possible, up to these constraints: + + It burns no more cycles than the amount obtained by combining `amount_high` and `amount_low`. Cycles are represented by 128-bit values. + + It burns no more cycles than the amount of cycles available for spending `liquid_balance(balance, reserved_balance, freezing_limit)`, where `reserved_balance` are cycles reserved for resource payments and `freezing_limit` is the amount of idle cycles burned by the canister during its `freezing_threshold`. + + It can be called multiple times, each time possibly burning more cycles from the balance. + + This call also copies the amount of cycles that were actually burned starting at the location `dst` in the canister memory. + + This system call does not trap. + +- `ic0.msg_cycles_refunded : () → i64` + + This function can only be used in a callback handler (reply or reject), and indicates the amount of cycles that came back with the response as a refund. The refund has already been added to the canister balance automatically. + +:::note + +This call traps if the amount of cycles refunded does not fit into a 64-bit value. In general, it is recommended to use `ic0.msg_cycles_refunded128` instead. + +::: + +- `ic0.msg_cycles_refunded128 : (dst : I) → ()`; `I ∈ {i32, i64}` + + This function can only be used in a callback handler (reply or reject), and indicates the amount of cycles that came back with the response as a refund. The refund has already been added to the canister balance automatically. + +### Stable memory {#system-api-stable-memory} + +:::note + +The 32-bit stable memory System API (`ic0.stable_size`, `ic0.stable_grow`, `ic0.stable_write`, and `ic0.stable_read`) is DEPRECATED. Canister developers are advised to use the 64-bit stable memory System API instead. + +::: + +Canisters have the ability to store and retrieve data from a secondary memory. The purpose of this *stable memory* is to provide space to store data beyond upgrades. The interface mirrors roughly the memory-related instructions of WebAssembly, and tries to be forward compatible with exposing this feature as an additional memory. + +The stable memory is initially empty and can be grown up to the [Wasm stable memory limit](../cycles-costs.md) (provided the subnet has capacity). + +- `ic0.stable_size : () → (page_count : i32)` + + returns the current size of the stable memory in WebAssembly pages. (One WebAssembly page is 64KiB) + + This system call traps if the size of the stable memory exceeds 232 bytes. + +- `ic0.stable_grow : (new_pages : i32) → (old_page_count : i32)` + + tries to grow the memory by `new_pages` many pages containing zeroes. + + This system call traps if the *previous* size of the memory exceeds 232 bytes. + + If the *new* size of the memory exceeds 232 bytes or growing is unsuccessful, then it returns `-1`. + + Otherwise, it grows the memory and returns the *previous* size of the memory in pages. + +- `ic0.stable_write : (offset : i32, src : i32, size : i32) → ()` + + copies the data referred to by `src`/`size` out of the canister and replaces the corresponding segment starting at `offset` in the stable memory. + + This system call traps if the size of the stable memory exceeds 232 bytes. + + It also traps if `src+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory. + +- `ic0.stable_read : (dst : i32, offset : i32, size : i32) → ()` + + copies the data referred to by `offset`/`size` out of the stable memory and replaces the corresponding bytes starting at `dest` in the canister memory. + + This system call traps if the size of the stable memory exceeds 232 bytes. + + It also traps if `dst+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory + +- `ic0.stable64_size : () → (page_count : i64)` + + returns the current size of the stable memory in WebAssembly pages. (One WebAssembly page is 64KiB) + +- `ic0.stable64_grow : (new_pages : i64) → (old_page_count : i64)` + + tries to grow the memory by `new_pages` many pages containing zeroes. + + If successful, returns the *previous* size of the memory (in pages). Otherwise, returns `-1`. + +- `ic0.stable64_write : (offset : i64, src : i64, size : i64) → ()` + + Copies the data from location \[src, src+size) of the canister memory to location \[offset, offset+size) in the stable memory. + + This system call traps if `src+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory. + +- `ic0.stable64_read : (dst : i64, offset : i64, size : i64) → ()` + + Copies the data from location \[offset, offset+size) of the stable memory to the location \[dst, dst+size) in the canister memory. + + This system call traps if `dst+size` exceeds the size of the WebAssembly memory or `offset+size` exceeds the size of the stable memory. + +### System time {#system-api-time} + +The canister can query the IC for the current time. + +`ic0.time : () -> i64` + +The time is given as nanoseconds since 1970-01-01. The IC guarantees that + +- the time, as observed by the canister, is monotonically increasing, even across canister upgrades. + +- within an invocation of one entry point, the time is constant. + +The times observed by different canisters are unrelated, and calls from one canister to another may appear to travel "backwards in time". + +:::note + +While an implementation will likely try to keep the time returned by `ic0.time` close to the real time, this is not formally part of this specification. + +::: + +### Global timer + +The canister can set a global timer to make the system schedule a call to the exported `canister_global_timer` Wasm method after the specified time. The time must be provided as nanoseconds since 1970-01-01. + +`ic0.global_timer_set : (timestamp : i64) -> i64` + +The function returns the previous value of the timer. If no timer is set before invoking the function, then the function returns zero. + +Passing zero as an argument to the function deactivates the timer and thus prevents the system from scheduling calls to the canister's `canister_global_timer` Wasm method. + +### Performance counter {#system-api-performance-counter} + +The canister can query one of the "performance counters", which is a deterministic monotonically increasing integer approximating the amount of work the canister has done. Developers might use this data to profile and optimize the canister performance. + +`ic0.performance_counter : (counter_type : i32) -> i64` + +The argument `type` decides which performance counter to return: + +- 0 : current execution instruction counter. The number of WebAssembly instructions the canister has executed since the beginning of the current [Message execution](./abstract-behavior.md#rule-message-execution). + +- 1 : call context instruction counter. + + - For replicated message execution, it is the number of WebAssembly instructions the canister has executed within the call context of the current [Message execution](./abstract-behavior.md#rule-message-execution) since [Call context creation](./abstract-behavior.md#call-context-creation). The counter monotonically increases across all [message executions](./abstract-behavior.md#rule-message-execution) in the call context until the corresponding [call context is removed](./abstract-behavior.md#call-context-removal). + + - For non-replicated message execution, it is the number of WebAssembly instructions the canister has executed within the corresponding `composite_query_helper` in [Query call](./abstract-behavior.md#query-call). The counter monotonically increases across the executions of the composite query method and the composite query callbacks until the corresponding `composite_query_helper` returns (ignoring WebAssembly instructions executed within any further downstream calls of `composite_query_helper`). + +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. + +`ic0.in_replicated_execution : () -> (result: i32)` + +Returns 1 if the canister is being run in replicated mode and 0 otherwise. + +### Controller check {#system-api-controller-check} + +The canister can check whether a given principal is one of its controllers. + +`ic0.is_controller : (src : I, size : I) -> (result: i32)`; `I ∈ {i32, i64}` + +Checks whether the principal identified by `src`/`size` is one of the controllers of the canister. If yes, then a value of 1 is returned, otherwise a 0 is returned. It can be called multiple times. + +This system call traps if `src+size` exceeds the size of the WebAssembly memory or the principal identified by `src`/`size` is not a valid binary encoding of a principal. + +### Certified data {#system-api-certified-data} + +For each canister, the IC keeps track of "certified data", a canister-defined blob. For fresh canisters (upon install or reinstall), this blob is the empty blob (`""`). + +- `ic0.root_key_size: () → I` and `ic0.root_key_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + Copies the public key (a DER-encoded BLS key) of the IC root key of this instance of the Internet Computer Protocol to the canister. + + This traps in non-replicated mode (NRQ, TQ, CQ, CRy, CRt, CC, and F modes, as defined in [Overview of imports](#system-api-imports)). + +- `ic0.certified_data_set : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` + + The canister can update the certified data with this call. The passed data must be no larger than 32 bytes. This can be used any number of times. + +When executing a query or composite query method via a query call (i.e. in non-replicated mode), the canister can fetch a certificate that authenticates to third parties the value last set via `ic0.certified_data_set`. The certificate is not available in composite query method callbacks and in query and composite query methods evaluated on canisters other than the target canister of the query call. + +- `ic0.data_certificate_present : () -> i32` + + returns `1` if a certificate is present, and `0` otherwise. + + This will return `1` when called from a query or composite query method on the target canister of a query call. + + This will return `0` for update methods, if a query method is executed in replicated mode (e.g. when invoked via an update call or inter-canister call) or as canister http outcall transform, and in composite query method callbacks and in query and composite query methods evaluated on canisters other than the target canister of a query call. + +- `ic0.data_certificate_size : () → I` and `ic0.data_certificate_copy : (dst : I, offset : I, size : I) → ()`; `I ∈ {i32, i64}` + + Copies the certificate for the current value of the certified data to the canister. + + The certificate is a blob as described in [Certification](./certification.md#certification) that contains the values at path `/canister//certified_data` and at path `/time` of [The system state tree](./index.md#state-tree). + + If this `certificate` includes a subnet delegation, then the id of the current canister will be included in the delegation's canister id range. + + This traps if `ic0.data_certificate_present()` returns `0`. + +### Cycle cost calculation {#system-api-cycle-cost} + +Inter-canister calls have an implicit cost, and some calls to the management canister require the caller to attach cycles to the call explicitly. +The various cost factors may change over time, so the following system calls give the canister programmatic, up-to-date information about the costs. + +These system calls return costs in Cycles, represented by 128 bits, which will be written to the heap memory starting at offset `dst`. Note that the cost calculation is only correct for correct inputs, e.g., a method name length argument should not exceed 20'000, because such an argument would be rejected by `ic0.call_new`. The cost API will still return a number in this case, but it would not have a real meaning. + +- `ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> ()`; `I ∈ {i32, i64}` + + This system call returns the amount of cycles that a canister needs to be above the freezing threshold in order to successfully make an inter-canister call. This includes the base cost for an inter-canister call, the cost for each byte transmitted in the request, the cost for the transmission of the largest possible response, and the cost for executing the largest possible response callback. The last two are cost _reservations_, which must be possible for a call to succeed, but they will be partially refunded if the real response and callback are smaller. So the cost of the actual inter-canister call may be less than this system call predicts, but it cannot be more. + `method_name_size` is the byte length of the method name, and `payload_size` is the byte length of the argument to the method. + +- `ic0.cost_create_canister : (dst : I) -> ()`; `I ∈ {i32, i64}` + + The cost of creating a canister on the same subnet as the calling canister via [`create_canister`](./management-canister.md#ic-create_canister). Note that canister creation via a call to the CMC can have a different cost if the target subnet has a different replication factor. + +- `ic0.cost_http_request(request_size : i64, max_res_bytes : i64, dst : I) -> ()`; `I ∈ {i32, i64}` + + The cost of a canister http outcall via [`http_request`](./management-canister.md#ic-http_request). `request_size` is the sum of the byte lengths of the following components of an http request: + - url + - headers - i.e., the sum of the lengths of all keys and values + - body + - transform - i.e., the sum of the transform method name length and the length of the transform context + + `max_res_bytes` is the maximum response length the caller wishes to accept (the caller should provide the default value of `2,000,000` if no maximum response length is provided in the actual request to the management canister). + +- `ic0.cost_sign_with_ecdsa(src : I, size : I, ecdsa_curve: i32, dst : I) -> i32`; `I ∈ {i32, i64}` + +- `ic0.cost_sign_with_schnorr(src : I, size : I, algorithm: i32, dst : I) -> i32`; `I ∈ {i32, i64}` + +- `ic0.cost_vetkd_derive_key(src : I, size : I, vetkd_curve: i32, dst : I) -> i32`; `I ∈ {i32, i64}` + + These system calls accept a key name via a textual representation for the specific signing scheme / key of a given size stored in the heap memory starting at offset `src`. They also accept an `i32` with the following interpretations: + - `ecdsa_curve: 0 → secp256k1` + - `algorithm: 0 → bip340secp256k1, 1 → ed25519` + - `vetkd_curve: 0 → bls12_381` + + See [`sign_with_ecdsa`](./management-canister.md#ic-sign_with_ecdsa), [`sign_with_schnorr`](./management-canister.md#ic-sign_with_schnorr) and [`vetkd_encrypted_key`](#ic-vetkd_encrypted_key) for more information. + + These system calls trap if `src` + `size` or `dst` + 16 exceed the size of the WebAssembly memory. Otherwise, they return an `i32` with the following meaning: + - `0`: Success. The result can be found at the memory address `dst`. + - `1`: Invalid curve or algorithm. Memory at `dst` is left unchanged. + - `2`: Invalid key name for the given combination of signing scheme and (valid) curve/algorithm. Memory at `dst` is left unchanged. + +### Environment Variables + +The following system calls provide access to the canister's environment variables: + +- `ic0.env_var_count : () -> I`; `I ∈ {i32, i64}` + + Returns the number of environment variables set for this canister. + +- `ic0.env_var_name_size : (index: I) -> I`; `I ∈ {i32, i64}` + + Gets the size in bytes of the name of the environment variable at the given index. + + This system call traps if: + - If the index is out of bounds (>= than value provided by `ic0.env_var_count`) + +- `ic0.env_var_name_copy : (index: I, dst: I, offset: I, size: I) -> ()`; `I ∈ {i32, i64}` + + Copies the name of the environment variable at the given index into memory. + + This system call traps if: + - The index is out of bounds (>= than value provided by `ic0.env_var_count`) + - `offset+size` is greater than the size of the environment variable name + - `dst+size` exceeds the size of the WebAssembly memory + + +- `ic0.env_var_name_exists : (name_src: I, name_size: I) -> i32`; `I ∈ {i32, i64}` + + Checks if an environment variable with the given name exists. If yes, then a value of 1 is returned, otherwise a 0 is returned. + + This system call traps if: + - `name_size` exceeds the maximum length of a variable name + - `name_src+name_size` exceeds the size of the WebAssembly memory + - If the data referred to by `name_src`/`name_size` is not valid UTF8. + + +- `ic0.env_var_value_size : (name_src: I, name_size: I) -> I`; `I ∈ {i32, i64}` + + Gets the size in bytes of the value for the environment variable with the given name. + + This system call traps if: + - `name_size` exceeds the maximum length of a variable name + - `name_src+name_size` exceeds the size of the WebAssembly memory + - If the data referred to by `name_src`/`name_size` is not valid UTF8. + - The name does not match any existing environment variable. + +- `ic0.env_var_value_copy : (name_src: I, name_size: I, dst: I, offset: I, size: I) -> ()`; `I ∈ {i32, i64}` + + Copies the value of the environment variable with the given name into memory. + + This system call traps if: + - `name_size` exceeds the maximum length of a variable name + - `name_src+name_size` exceeds the size of the WebAssembly memory + - If the data referred to by `name_src`/`name_size` is not valid UTF8. + - The name does not match any existing environment variable. + - `offset+size` is greater than the size of the environment variable value + - `dst+size` exceeds the size of the WebAssembly memory + +These system calls allow canisters to: +- Enumerate all environment variables +- Look up values by name + +### Debugging aids + +Canister can produce logs available through the management canister endpoint [`fetch_canister_logs`](./management-canister.md#ic-fetch_canister_logs). + +- `ic0.debug_print : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` + + This copies out the data specified by `src` and `size` and appends that data to canister logs. + The data can be trimmed to an implementation defined maximum size. + + This function never traps, even if the `src+size` exceeds the size of the memory (in which case system-generated data are used instead). + +Similarly, the System API allows the canister to effectively trap and give some indication about why it trapped: + +- `ic0.trap : (src : I, size : I) -> ()`; `I ∈ {i32, i64}` + + This copies out the data specified by `src` and `size` and appends that data to canister logs. + The data can be trimmed to an implementation defined maximum size. + + Moreover, the data specified by `src` and `size` might be included in a reject message + (trimmed to an implementation defined maximum size and omitting bytes that are not valid UTF-8). + + This function always traps. + +### Outlook: Using Host References {#host-references} + +The Internet Computer aims to make the most of the WebAssembly platform, and embraces WebAssembly features. With WebAssembly host references, we can make the platform more secure, the interfaces more abstract and more compositional. The above `ic0` System API does not yet use WebAssembly host references. Once they become available on our platform, a new version of the System API using host references will be available via the `ic` module. The changes will be, at least + +1. The introduction of a `api_nonce` reference, which models the capability to use the System API. It is passed as an argument to `canister_init`, `canister_update ` etc., and expected as an argument by almost all System API function calls. (The debugging aids remain unconstrained.) + +2. The use of references, instead of binary blobs, to address principals (user ids, canister ids), e.g. in `ic0.msg_caller` or in `ic0.call_new`. Additional functions will be provided to convert between the transparent binary representation of principals and references. + +3. Making the builder interface to create calls build calls identified by a reference, rather than having an implicit partial call in the background. + +A canister may only use the old *or* the new interface; the IC detects which interface the canister intends to use based on the names and types of its function imports and exports. + + diff --git a/docs/references/ic-interface-spec/certification.md b/docs/references/ic-interface-spec/certification.md new file mode 100644 index 0000000..3c1acba --- /dev/null +++ b/docs/references/ic-interface-spec/certification.md @@ -0,0 +1,256 @@ +--- +title: "Certification" +description: "Certified state trees, delegation chains, certificate encoding, and the HTTP Gateway protocol" +sidebar: + label: "Certification" + order: 4 +--- + +## Certification {#certification} + +Some parts of the IC state are exposed to users in a tamperproof way via certification: the IC can reveal a *partial state tree* which includes just the data of interest, together with a signature on the root hash of the state tree. This means that a user can be sure that the response is correct, even if the user happens to be communicating with a malicious node, or has received the certificate via some other untrusted way. + +To validate a value using a certificate, the user conceptually + +1. checks the validity of the partial tree using `verify_cert`, + +2. looks up the value in the certificate using `lookup` at a given path, which uses the subroutine `lookup_path` on the certificate's tree. + +This mechanism is used in the `read_state` request type, and eventually also for other purposes. + +### Root of trust + +The root of trust is the *root public key*, which must be known to the user a priori. In a local canister execution environment, the key can be fetched via the [`/api/v2/status`](./https-interface.md#api-status) endpoint. + +### Certificate + +A certificate consists of + +- a tree + +- a signature on the tree root hash valid under some *public key* + +- an optional *delegation* that links that public key to *root public key*. + +The IC will certify states by issuing certificates where the tree is a partial state tree. The state tree can be pruned by replacing subtrees with their root hashes (yielding a new and potentially smaller but still valid certificate) to only include paths pertaining to relevant data but still preserving enough information to recover the *tree root hash*. + +More formally, a certificate is described by the following data structure: +``` +Certificate = { + tree : HashTree + signature : Signature + delegation : NoDelegation | Delegation +} +HashTree + = Empty + | Fork HashTree HashTree + | Labeled Label HashTree + | Leaf blob + | Pruned Hash +Label = Blob +Hash = Blob +Signature = Blob +``` + +A certificate is validated with regard to the root of trust by the following algorithm (which uses `check_delegation` defined in [Delegation](#certification-delegation)): + + verify_cert(cert) = + let root_hash = reconstruct(cert.tree) + // see section Delegations below + if check_delegation(cert.delegation) = false then return false + let bls_key = delegation_key(cert.delegation) + verify_bls_signature(bls_key, cert.signature, domain_sep("ic-state-root") · root_hash) + + reconstruct(Empty) = H(domain_sep("ic-hashtree-empty")) + reconstruct(Fork t1 t2) = H(domain_sep("ic-hashtree-fork") · reconstruct(t1) · reconstruct(t2)) + reconstruct(Labeled l t) = H(domain_sep("ic-hashtree-labeled") · l · reconstruct(t)) + reconstruct(Leaf v) = H(domain_sep("ic-hashtree-leaf") · v) + reconstruct(Pruned h) = h + + domain_sep(s) = byte(|s|) · s + +where `H` is the SHA-256 hash function, + + verify_bls_signature : PublicKey -> Signature -> Blob -> Bool + +is the [BLS signature verification function](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-4), ciphersuite BLS\_SIG\_BLS12381G1\_XMD:SHA-256\_SSWU\_RO\_NUL\_. See that document also for details on the encoding of BLS public keys and signatures. + +All state trees include the time at path `/time` (see [Time](./index.md#state-tree-time)). Users that get a certificate with a state tree can look up the timestamp to guard against working on obsolete data. + +### Lookup {#lookup} + +Given a (verified) tree, the user can fetch the value at a given path, which is a sequence of labels (blobs). In this document, we write paths suggestively with slashes as separators; the actual encoding is not actually using slashes as delimiters. + +The following algorithm looks up a `path` in a certificate, and returns either + +- `Found v`: the requested `path` has an associated value `v` in the tree, + +- `Absent`: the requested path is not in the tree, + +- `Unknown`: it cannot be syntactically determined if the requested `path` was pruned or not; i.e., there exist at least two trees (one containing the requested path and one *not* containing the requested path) from which the given tree can be obtained by pruning some subtrees, + +- `Error`: the requested path does not have an associated value in the tree, but the requested path is in the tree: + +```html + +lookup(path, cert) = lookup_path(path, cert.tree) + +lookup_path([], Empty) = Absent +lookup_path([], Leaf v) = Found v +lookup_path([], Pruned _) = Unknown +lookup_path([], Labeled _ _) = Error +lookup_path([], Fork _ _) = Error + +lookup_path(l::ls, tree) = + match find_label(l, flatten_forks(tree)) with + | Absent -> Absent + | Unknown -> Unknown + | Error -> Error + | Found subtree -> lookup_path ls subtree + +flatten_forks(Empty) = [] +flatten_forks(Fork t1 t2) = flatten_forks(t1) · flatten_forks(t2) +flatten_forks(t) = [t] + +find_label(l, _ · Labeled l1 t · _) | l == l1 = Found t +find_label(l, _ · Labeled l1 _ · Labeled l2 _ · _) | l1 < l < l2 = Absent +find_label(l, Labeled l2 _ · _) | l < l2 = Absent +find_label(l, _ · Labeled l1 _ ) | l1 < l = Absent +find_label(l, [Leaf _]) = Absent +find_label(l, []) = Absent +find_label(l, _) = Unknown + +``` + +Given a path `prefix`, we define `lookup*(prefix, cert)` to be the concatenation of all values at paths with the given prefix, +i.e., for every `path` of the form `path = prefix · _` with `lookup(path, cert) = Found v`. + +The IC will only produce well-formed state trees, and the above algorithm assumes well-formed trees. These have the property that labeled subtrees appear in strictly increasing order of labels, and are not mixed with leaves. More formally: + + well_formed(tree) = + (tree = Leaf _) ∨ (well_formed_forest(flatten_forks(tree))) + + well_formed_forest(trees) = + strictly_increasing([l | Label l _ ∈ trees]) ∧ + ∀ Label _ t ∈ trees. well_formed(t) ∧ + ∀ t ∈ trees. t ≠ Leaf _ + +### Delegation {#certification-delegation} + +The root key can delegate certification authority to other keys. + +A certificate by the root subnet does not have a delegation field. A certificate by other subnets include a delegation, which is itself a certificate that proves that the subnet is listed in the root subnet's state tree (see [Subnet information](./index.md#state-tree-subnet)), and reveals its public key. + +:::note + +The certificate included in the delegation (if present) must not itself again contain a delegation. + +::: + +``` +Delegation = { + subnet_id : Principal; + certificate : Certificate; + } +``` + +A delegation is verified using the following algorithm: +``` +check_delegation(NoDelegation) = true +check_delegation(Delegation d) = verify_cert(d.certificate) and lookup(["subnet",d.subnet_id,"public_key"],d.certificate) = Found _ and d.certificate.delegation = NoDelegation +``` + +The delegation key (a BLS key) is computed by the following algorithm: +``` +delegation_key(NoDelegation) : public_bls_key = root_public_key +delegation_key(Delegation d) : public_bls_key = + match lookup(["subnet",d.subnet_id,"public_key"],d.certificate) with + Found der_key -> extract_der(der_key) +``` + +where `root_public_key` is the a priori known root key and +``` +extract_der : Blob -> Blob +``` + +implements DER decoding of the public key, following [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) using OID 1.3.6.1.4.1.44668.5.3.1.2.1 for the algorithm and 1.3.6.1.4.1.44668.5.3.2.1 for the curve. + +Delegations are *scoped*, i.e., they indicate which set of canister principals the delegatee subnet may certify for. This set can be obtained from a delegation `d` using `lookup*(["canister_ranges",d.subnet_id],d.certificate)`. See [lookup](#lookup) for the definition of `lookup*` and [Canister ranges](./index.md#state-tree-canister-ranges) for the description of the encoding used. The various applications of certificates describe if and how the subnet scope comes into play. + +### Encoding of certificates {#certification-encoding} + +The binary encoding of a certificate is a CBOR (see [CBOR](./index.md#cbor)) value according to the following CDDL (see [CDDL](./index.md#cddl)). You can also [download the file](_attachments/certificates.cddl). + +The values in the [The system state tree](./index.md#state-tree) are encoded to blobs as follows: + +- natural numbers are leb128-encoded. + +- text values are UTF-8-encoded + +- blob values are encoded as is + +### Example + +Consider the following tree-shaped data (all single character strings denote labels, all other denote values) + + ─┬╴ "a" ─┬─ "x" ─╴"hello" + │ └╴ "y" ─╴"world" + ├╴ "b" ──╴ "good" + ├╴ "c" + └╴ "d" ──╴ "morning" + +A possible hash tree for this labeled tree might be, where `┬` denotes a fork. This is not a typical encoding (a fork with `Empty` on one side can be avoided), but it is valid. + + ─┬─┬╴"a" ─┬─┬╴"x" ─╴"hello" + │ │ │ └╴Empty + │ │ └╴ "y" ─╴"world" + │ └╴"b" ──╴"good" + └─┬╴"c" ──╴Empty + └╴"d" ──╴"morning" + +This tree has the following CBOR (see [CBOR](./index.md#cbor)) encoding + + 8301830183024161830183018302417882034568656c6c6f810083024179820345776f726c6483024162820344676f6f648301830241638100830241648203476d6f726e696e67 + +and the following root hash + + eb5c5b2195e62d996b84c9bcc8259d19a83786a2f59e0878cec84c811f669aa0 + +Pruning this tree with the following paths + + /a/y + /ax + /d + +would lead to this tree (with pruned subtree represented by their hash): + + ─┬─┬╴"a" ─┬─ 1B4FEFF9BEF8131788B0C9DC6DBAD6E81E524249C879E9F10F71CE3749F5A638 + │ │ └╴ "y" ─╴"world" + │ └╴"b" ──╴7B32AC0C6BA8CE35AC82C255FC7906F7FC130DAB2A090F80FE12F9C2CAE83BA6 + └─┬╴EC8324B8A1F1AC16BD2E806EDBA78006479C9877FED4EB464A25485465AF601D + └╴"d" ──╴"morning" + +Note that the `"b"` label is included (without content) to prove the absence of the `/ax` path. + +This tree encodes to CBOR as + + 83018301830241618301820458201b4feff9bef8131788b0c9dc6dbad6e81e524249c879e9f10f71ce3749f5a63883024179820345776f726c6483024162820458207b32ac0c6ba8ce35ac82c255fc7906f7fc130dab2a090f80fe12f9c2cae83ba6830182045820ec8324b8a1f1ac16bd2e806edba78006479c9877fed4eb464a25485465af601d830241648203476d6f726e696e67 + +and (obviously) the same root hash. + +In the pruned tree, the `lookup_path` function behaves as follows: + + lookup_path(["a", "a"], pruned_tree) = Unknown + lookup_path(["a", "y"], pruned_tree) = Found "world" + lookup_path(["aa"], pruned_tree) = Absent + lookup_path(["ax"], pruned_tree) = Absent + lookup_path(["b"], pruned_tree) = Unknown + lookup_path(["bb"], pruned_tree) = Unknown + lookup_path(["d"], pruned_tree) = Found "morning" + lookup_path(["e"], pruned_tree) = Absent + +## The HTTP Gateway protocol {#http-gateway} + +The HTTP Gateway Protocol has been moved into its own [specification](../http-gateway-spec.md). + + diff --git a/docs/references/ic-interface-spec/changelog.md b/docs/references/ic-interface-spec/changelog.md new file mode 100644 index 0000000..8dbfbf1 --- /dev/null +++ b/docs/references/ic-interface-spec/changelog.md @@ -0,0 +1,492 @@ +--- +title: "IC Interface Spec Changelog" +description: "Version history and changes to the IC Interface Specification" +sidebar: + label: "Changelog" + order: 6 +--- + +## Changelog {#changelog} + +### 0.59.0 (2025-04-27) {$0_59_0} +* Update and query calls by users authenticated via canister signatures can carry + auxiliary information signed by a canister signature issued by the same canister. + That information is passed via a new `sender_info` field of the user HTTP request + and available to the callee via new system API `ic0.msg_caller_info_data_size`, + `ic0.msg_caller_info_data_copy`, `ic0.msg_caller_info_signer_size`, and + `ic0.msg_caller_info_signer_copy`. +* Added query call for subnet admins to list all canisters on a subnet. + +### 0.58.0 (2025-04-01) {$0_58_0} +* Added subnet admins who can start, stop, uninstall, delete a canister and request its status. + +### 0.57.0 (2025-03-30) {$0_57_0} +* Added canister snapshot visibility settings. + +### 0.56.0 (2025-03-16) {$0_56_0} +* Support for the HTTP methods `PUT` and `DELETE` in canister `http_request` in non-replicated mode. +* Added subnet type to the certified state tree at the path `/subnet//type`. + +### 0.55.0 (2025-01-26) {$0_55_0} +* Support for canister renaming (required for canister ID migration from one subnet to another subnet). + +### 0.54.0 (2025-12-15) {$0_54_0} +* The management canister's endpoint `take_canister_snapshot` can uninstall code atomically after taking canister snapshot. +* The canister system API `ic0.msg_reject_code` is available in cleanup callbacks. + +### 0.53.0 (2025-11-24) {$0_53_0} +* New execution context `TQ` for canister http transform functions to specify that no IC certificate is available in such executions. + +### 0.52.0 (2025-11-17) {$0_52_0} +* Canister memory allocation does not limit canister memory usage. + +### 0.51.0 (2025-10-20) {$0_51_0} +* Management canister endpoint `canister_status` can be invoked as a query call. + +### 0.50.0 (2025-10-13) {$0_50_0} +* Allow loading a canister snapshot onto a different canister on the same subnet. +* New management canister API providing canister metadata (contained in canister WASM custom sections). +* New HTTP handler endpoints to support canister migration (by providing sharded routing table in certificates): + `/api/v4/canister//call`, + `/api/v3/canister//read_state`, + `/api/v3/subnet//read_state`, and + `/api/v3/canister//query`. +* The following existing HTTP handler endpoints are marked as deprecated: + `/api/v3/canister//call`, + `/api/v2/canister//read_state`, + `/api/v2/subnet//read_state`, and + `/api/v2/canister//query`. +* Paths with prefix `/subnet//canister_ranges` (legacy routing table) can only be requested via the deprecated endpoints or if `` is the root subnet. +* Paths with prefix `/canister_ranges/` (sharded routing table) can only be requested via `/api/v{2,3}/subnet/.../read_state`, i.e., + not via `/api/v{2,3}/canister/.../read_state`. +* All paths with prefix `/canister_ranges/` must refer to the same subnet ID ``. + +### 0.49.0 (2025-10-06) {$0_49_0} +* Added support for non-replicated canister HTTP outcalls. + +### 0.48.0 (2025-09-29) {$0_48_0} +* Added support for canister environment variables in canister settings and new System API for accessing environment variables + (`ic0.env_var_count`, `ic0.env_var_name_size`, `ic0.env_var_name_copy`, and `ic0.env_var_value_size`). + +### 0.47.0 (2025-09-22) {$0_47_0} +* Management canister API for downloading and uploading canister snapshots. + +### 0.46.0 (2025-08-25) {$0_46_0} +* The management canister method `canister_status` returns two new fields: `version` indicating the canister version, and `ready_for_migration` indicating whether a canister's queues are empty and its streams flushed. The value only makes sense when the canister status is `stopped`. +* Canister history provides the source of a snapshot in the entry for loading the snapshot. + +### 0.45.0 (2025-08-18) {$0_45_0} +* Canister ranges of every subnet are now available at a dedicated prefix `/canister_ranges` in the state tree, + facilitating fragmentation due to canister migration. + +### 0.44.0 (2025-08-11) {$0_44_0} +* The management canister method `subnet_info` returns a new field `registry_version` providing the registry version of the corresponding subnet. + +### 0.43.0 (2025-07-17) {$0_43_0} +* VetKD API is considered stable. + +### 0.42.0 (2025-06-06) {#0_42_0} +* New system API `ic0.root_key_{size, copy}` for fetching the public key of the IC root key. + +### 0.41.0 (2025-06-02) {#0_41_0} +* Management canister API for threshold key derivation (vetKD). + +### 0.40.0 (2025-05-30) {#0_40_0} +* Non-ASCII characters are allowed in the URL of canister http outcalls. +* The transformed response size of canister http outcalls must not exceeded `max_response_bytes` (if provided). + +### 0.39.0 (2025-05-07) {#0_39_0} +* Threshold Schnorr API, composite query methods, and canister logs management canister API are considered stable. + +### 0.38.0 (2025-04-18) {#0_38_0} +* Reverted a lower bound of one week on the canister's freezing threshold. + +### 0.37.0 (2025-04-11) {#0_37_0} +* Introduced a lower bound of one week on the canister's freezing threshold. + +### 0.36.0 (2025-03-31) {#0_36_0} +* Bounded-wait calls. + +### 0.35.0 (2025-03-20) {#0_35_0} +* New system API `ic0.canister_liquid_cycle_balance128` returning the current amount of cycles that is available for spending in calls and execution. +* A canister can have multiple snapshots. + +### 0.34.0 (2025-03-07) {#0_34_0} +* New canister method `canister_on_low_wasm_memory` invoked when the canister is low on main memory according to a new `wasm_memory_threshold` in canister settings. +* New system APIs `ic0.cost_call`, `ic0.cost_create_canister`, `ic0.cost_http_request`, `ic0.cost_sign_with_ecdsa`, `ic0.cost_sign_with_schnorr`, and `ic0.cost_vetkd_derive_encrypted_key` for cycles cost calculation. +* New field `memory_metrics` providing detailed metrics on the memory consumption of a canister in the response of the management canister's `canister_status` endpoint. + +### 0.33.0 (2025-02-12) {#0_33_0} +* New system API `ic0.subnet_self_size` and `ic0.subnet_self_copy`. + +### 0.32.0 (2025-01-23) {#0_32_0} +* Allow accepting and burning cycles in replicated queries. + +### 0.31.0 (2025-01-09) {#0_31_0} +* Add support for Schnorr auxiliary inputs + +### 0.30.0 (2024-11-19) {#0_30_0} +* Add management canister endpoint `subnet_info`. +* Support for wasm64: 64-bit system API. + +### 0.29.0 (2024-11-14) {#0_29_0} +* Allow anonymous query and read state requests with invalid `ingress_expiry`. +* Add allowed viewers variant to canister log visibility. +* Deprecate the Bitcoin API of the management canister. + +### 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. + +### 0.25.0 (2024-06-14) {#0_25_0} +* Query call statistics. +* New `wasm_memory_persistence` option for canister upgrades. +* Rename `num_blocks_total` to `num_blocks_proposed_total` in node metrics served by the management canister. +* Management canister query call to fetch canister logs. +* WASM heap memory limit in canisters settings. +* 32-bit stable memory System API is marked DEPRECATED. +* Remove the management canister query calls `bitcoin_get_balance_query` and `bitcoin_get_utxos_query`. + +### 0.24.0 (2024-04-23) {#0_24_0} +* Wrap chunk hash for install chunked code in a record and rename `storage_canister` to `store_canister`. +* Update subnet read state request conditions on requested paths. +* Fix: allow inter-canister calls (requests) to be spontaneously rejected in the abstract spec. + +### 0.23.0 (2024-03-06) {#0_23_0} +* The maximum length of a nonce in an ingress message is 32 bytes. +* Update specification of responses from the endpoint `/api/v2/status`. +* Stop canister calls might be rejected upon timeout. +* The IC sends a `user-agent` header with the value `ic/1.0` in canister HTTPS outcalls if the canister does not provide one. +* Add a management canister method for retrieving node metrics. +* Specify the resource reservation mechanism. +* Allow `in_replicated_execution` system API method to be executed during `canister_start`. +* Set the maximum depth of a delegation in a read_state response/certified variable certificate to 1. +* Canister version is guaranteed to increase if the canister's running status changes. +* Calls to frozen canisters are rejected with `SYS_TRANSIENT` instead of `CANISTER_ERROR`. +* Add API boundary nodes information into the certified state tree. + +### 0.22.0 (2023-11-15) {#0_22_0} +* Add metrics on subnet usage into the certified state tree and a new HTTP endpoint `/api/v2/subnet//read_state` for retrieving them. +* Add management canister methods to support installing large WebAssembly modules split into chunks. +* Add a system API method to determine if the canister is running in replicated or non-replicated mode. +* Add a system API method to burn cycles of the canister that calls this method. +* Add a check that a canister receiving an ingress message is Running before the ingress message is marked as Received. +* Increase the maximum number of globals in a canister's WASM. +* Add per-call context performance counter. +* Update the computation of the representation-independent hash for the case of maps with nested maps. +* Remove `senders` field from user delegations. + +### 0.21.0 (2023-09-18) {#0_21_0} +* Canister cycle balance cannot decrease below the freezing limit after executing `install_code` on the management canister. +* System API calls `ic0.msg_caller_size` and `ic0.msg_caller_copy` can be called in all contexts except for (start) function. +* Added note on confidentiality of values in the certified state tree. +* Update algorithm computing the request and response hash in the HTTP Gateway including clarification of when the HTTP Gateway can allow for arbitrary certification version in the canister's response. +* Update conditions on requested paths in HTTP read state requests. +* Added new query methods in the Bitcoin API. +* Added node public keys to certified state and node signatures to query call responses. +* Added a new mode for canister upgrades skipping pre-upgrade method's execution. + +### 0.20.0 (2023-07-11) {#0_20_0} +* IC Bitcoin API, ECDSA API, canister HTTPS outcalls API, and 128-bit cycles System API are considered stable. +* Add conditions on requested paths in read state requests. +* Add composite queries. +* Specify that the canister version is incremented upon every successful message execution except for successful message execution of a query method. + +### 0.19.0 (2023-06-08) {#0_19_0} +* canister version can be specified in some management canister calls (canister creation, canister code changes, canister settings changes) +* IC records canister history (canister creation, canister code changes, and canister controllers changes) +* added a new `canister_info` management canister call returning current module hash, current controllers, and canister history +* added a new system API call `ic0.is_controller` (checking if a principal is a controller of the canister) +* stable memory System API calls can be invoked in the WebAssembly module `(start)` function +* the system API call `ic0.global_timer_set` can be invoked in canister pre-upgrade +* added modeling WASM start function in the concrete `CanisterModule` specification +* WebAssembly module requirements have been revised (relaxed max number of declared functions and globals, added conditions on exported functions) +* certified variables are cleared if a canister is reinstalled +* a canister having an open call context marked as deleted cannot reach Stopped state +* a desired canister ID of the canister created by `provisional_create_canister_with_cycles` (in testing environments) can be specified using `specified_id` +* conditions on envelope delegations have been revised (relaxed max number of delegations, restricted max number of targets per delegation, forbidden cycles in the delegation chain) +* added a new optional field `senders` in envelope delegations (restricting users to which a delegation applies) +* all `/request_status/` paths must refer to the same `request_id` in a `read_state` request +* IC protocol execution error conditions (such as failing `inspect_message` method of a canister) are returned as 200 HTTP responses with a cbor body describing the error (instead of 4xx or 5xx HTTP responses) + +### 0.18.9 (2022-12-06) {#0_18_9} +* Global timers +* Canister version +* Clarifications for HTTP requests & Bitcoin integration costs + +### 0.18.8 (2022-11-09) {#0_18_8} +* Updated HTTP request API +* Canister status available to canister +* 64-bit stable memory is no longer experimental + +### 0.18.7 (2022-09-27) {#0_18_7} +* HTTP request API +* Reserved principals + +### 0.18.6 (2022-08-09) {#0_18_6} +* Canister access to performance metrics +* Query calls are rejected when the canister is frozen +* Support for implementation-specific error codes for requests +* Deleted call contexts do not prevent canister from reaching Stopped state +* Update effective canister id checks in certificate delegations +* Formal model in Isabelle + +### 0.18.5 (2022-07-08) {#0_18_5} +* Idle consumption of resources in cycles per day can be obtain via `canister_status` method of the management canister +* Include the HTTP Gateway Protocol in this spec +* Clarifications in definition of cycles consumption + +### 0.18.4 (2022-06-20) {#0_18_4} + +* Canister cycle balances are represented by 128 bits, and no system-defined upper limit exists anymore +* Canister modules can be gzip-encoded +* Expose Wasm custom sections in the state tree +* EXPERIMENTAL: Canister API for accessing Bitcoin transactions +* EXPERIMENTAL: Canister API for threshold ECDSA signatures + +### 0.18.3 (2022-01-10) {#0_18_3} + +* New System API which uses 128-bit values to represent the amount of cycles +* Subnet delegations include a canister id scope + +### 0.18.2 (2021-09-29) {#0_18_2} + +* Canister heartbeat +* Terminology changes +* Support for 64-bit stable memory + + +### 0.18.1 (2021-08-04) {#0_18_1} + +* Support RSA PKCS#1 v1.5 signatures in web authentication +* Spec clarification: Fix various typos and improve textual clarity + + +### 0.18.0 (2021-05-18) {#0_18_0} + +* A canister has a set of controllers, instead of always one + + +### 0.17.0 (2021-04-22) {#0_17_0} + +* Canister Signatures are introduced +* Spec clarification: the signature in the WebAuthn scheme is prefixed by the CBOR self-identifying tag +* Cycle-depleted canisters are forcibly uninstalled +* Canister settings in `create_canister` and `update_settings`. `install_code` no longer takes allocation settings. +* A freezing threshold can be configured via the canister settings + +### 0.16.1 (2021-04-14) {#0_16_1} +* The cleanup callback is introduced + +### 0.16.0 (2021-03-25) {#0_16_0} + +* New http v2 API that allows for stateless boundary nodes + +### 0.15.6 (2021-03-25) {#0_15_6} + +* The system may impose limits on the number of globals and functions +* No ingress messages towards empty canisters are accepted +* No ingress messages towards `raw_rand` and `deposit_cycles` are accepted +* A memory allocation of `0` means “best effort” + + +### 0.15.5 (2021-03-11) {#0_15_5} + +* deposit_cycles(): any caller allowed + + +### 0.15.4 (2021-03-04) {#0_15_4} + +* Ingress message filtering +* Add ECDSA signatures on curve secp256k1 +* Clarify that the `ic0.data_certificate_present` system function may be + called in all contexts. + + +### 0.15.3 (2021-02-26) {#0_15_3} + +* Expose module hash and controller via `read_state` + + +### 0.15.2 (2021-02-09) {#0_15_2} + +* The document is renamed to “Internet Computer Interface Spec” + + +### 0.15.0 (2020-12-17) {#0_15_0} + +* Support for raw Ed25519 keys is removed + + +### 0.14.1 (2020-12-08) {#0_14_1} + +* The default `memory_allocation` becomes unspecified + + +### 0.14.0 (2020-11-18) {#0_14_0} + +* Support for funds is scaled back to only support cycles +* The `ic0.msg_cycles_accept` system call now returns the actually accepted + cycles +* The `provisional_` management calls are introduced + + +### 0.13.2 (2020-11-12) {#0_13_2} + +* The `ic0.canister_status` system call + + +### 0.13.1 (2020-11-06) {#0_13_1} + +* Delegation between user public keys + + +### 0.13.0 (2020-10-19) {#0_13_0} + +* Certification (also removes “request-status” request) + + +### 0.12.2 (2020-10-23) {#0_12_2} + +* User authentication method based on WebAuthn is introduced +* User authentication can use ECDSA +* Public keys are DER-encoded + + +### 0.12.1 (2020-10-16) {#0_12_1} + +* Return more information in the `canister_status` management call + + +### 0.12.0 (2020-10-13) {#0_12_0} + +* Anonymous requests must have the sender field set + + +### 0.11.1 (2020-10-01) {#0_11_1} + +* The `deposit_funds` call + + +### 0.11.0 (2020-09-23) {#0_11_0} + +* Inter-canister calls are now performed using a builder-like API +* Support for funds (balances and transfers) + + +### 0.10.3 (2020-09-21) {#v0_10_3} + +* The anonymous user is introduced + + +### 0.10.1 (2020-09-01) {#v0_10_1} + +* Forward-port changes from 0.9.3 + + +### 0.10.0 (2020-08-06) {#v0_10_0} + +* Users can set/update a memory allocation when installing/upgrading a canister. +* The `expiry` field is added to requests + + +### 0.9.3 (2020-09-01) {#v0_9_3} + +* The management canister supports the `raw_rand` method + + +### 0.9.2 (2020-08-05) {#v0_9_2} + +* Canister controllers can stop/start canisters and can query their status. +* Canister controllers can delete canisters + + +### 0.9.1 (2020-07-20) {#v0_9_1} + +* Forward-port changes from 0.8.2 + + +### 0.9.0 (2020-07-15) {#v0_9_0} + +* Introduction of a domain separator (again) +* The calculation of “derived ids” has changed +* The self-authenticating and derived id forms use a truncated hash +* The textual representation of principals has changed + + +### 0.8.2 (2020-07-17) {#v0_8_2} + +* Installing code via `reinstall` works also on the empty canister + + +### 0.8.1 (2020-07-10) {#v0_8_1} + +* Reflect refined process in README and intro. +* `ic0.time` added + + +### 0.8.0 (2020-06-23) {#v0_8_0} + +* Revert the introduction of a domain separator + + +### 0.6.2 (2020-06-23) {#v0_6_2} + +* Fix meaning-changing typos in `ic.did` + + +### 0.6.0 (2020-06-08) {#v0_6_0} + +* Make all canister ids system-chosen +* HTTP requests for management features are removed + + +### 0.4.0 (2020-05-25) {#v0_4_0} + +* (editorial) the term “principal” is now used for the _id_ of a canister or + user, not the canister or user itself +* The signature of a request needs to be calculated using a domain separator +* Describe the `controller` attribute, add a request to change it +* The IC management canister is introduced + + +### 0.2.16 (2020-05-29) {#v0_2_16} + +* More tests about calls from query methods + + +### 0.2.14 (2020-05-14) {#v0_2_14} + +* Bugfix: Mode should be `reinstall`, not `replace` + + +### 0.2.8 (2020-04-23) {#v0_2_8} + +* Include section with CDDL description + + +### 0.2.4 (2020-03-23) {#v0_2_4} + +* simplify versioning (only three components), skip 0.2.2 to avoid confusion with 0.2.0.2 +* Clarification: `reply` field is always present +* General cleanup based on front-to-back reading + + +### 0.2.0.0 (2020-03-11) {#v0_2_0_0} + +* This is the first release. Subsequent releases will include a changelog. + + + + diff --git a/docs/references/ic-interface-spec/https-interface.md b/docs/references/ic-interface-spec/https-interface.md new file mode 100644 index 0000000..c10f34c --- /dev/null +++ b/docs/references/ic-interface-spec/https-interface.md @@ -0,0 +1,745 @@ +--- +title: "HTTPS Interface" +description: "HTTP endpoints for submitting calls, reading state, and querying canisters on the Internet Computer" +sidebar: + label: "HTTPS Interface" + order: 1 +--- + +## HTTPS Interface {#http-interface} + +The concrete mechanism that users use to send requests to the Internet Computer is via an HTTPS API, which exposes four endpoints to handle interactions, plus one for diagnostics: + +- At `/api/v2/canister//call`, the user can submit update calls that are asynchronous and might change the IC state. + +- At `/api/v3/canister//call` (deprecated) and `/api/v4/canister//call`, the user can submit update calls and get a synchronous HTTPS response with a certificate for the call status. + +- At `/api/v2/canister//read_state` (deprecated), `/api/v2/subnet//read_state` (deprecated), `/api/v3/canister//read_state`, and `/api/v3/subnet//read_state`, the user can read various information about the state of the Internet Computer. In particular, they can poll for the status of a call here. + +- At `/api/v2/canister//query` (deprecated) and `/api/v3/canister//query`, the user can perform (synchronous, non-state-changing) query calls. + +- At `/api/v2/status` the user can retrieve status information about the Internet Computer. + +In these paths, the `` is the [textual representation](./index.md#textual-ids) of the [*effective* canister id](#http-effective-canister-id). + +Requests to `/api/.../call`, `/api/.../read_state`, and `/api/.../query` are POST requests with a CBOR-encoded request body, which consists of a authentication envelope (as per [Authentication](#authentication)) and request-specific content as described below. + +:::note + +This document does not yet explain how to find the location and port of the Internet Computer. + +::: + +### 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. + +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) and the call is received by a replica (a node belonging to an IC subnet). The receiving replica decides whether it accepts the call. An honest replica does so by checking that the target canister is not frozen and + + - checking that the target canister is not empty, checking that the target canister is running, and performing [ingress message inspection](./canister-interface.md#system-api-inspect-message) for calls to a regular canister; + + - checking that the management canister method can be called via ingress messages and that the caller is a controller of the target canister for calls to the management canister + (or that the call targets the [IC Provisional API](./management-canister.md#ic-provisional-api) on a development instance). + + Moreover, the signature must be valid and created with a correct key. + + Finally, the system time (of the replica receiving the HTTP request) must not have exceeded the `ingress_expiry` field of the HTTP request containing the call. + + From this point on the user may receive a response from the IC about the status of the call. Only valid IC certificates in responses should be trusted, since responses come from a single replica that can be either honest or malicious. Note that a lack of a valid IC certificate doesn't necessarily mean that the responding replica is malicious; examples of responses that are expected to come without a certificate (and thus aren't necessarily trustworthy) include responses signalling that the message hasn't been accepted, and responses saying that the request is accepted for further processing. + + So far the corresponding IC subnet (as a whole) still behaves as if it does not know about the call. + + At some point, the IC subnet (as a whole) receives the call and sets its (certified) status to `received`. + + The above steps are formalized in this [transition](./abstract-behavior.md#api-request-submission). + +2. Once the IC starts processing the call, its (certified) status is set to `processing`. This transition can only happen before the target canister's time (as visible in the [state tree](./index.md#state-tree-time)) exceeds the [`ingress_expiry`](#http-call) field of the HTTP request which contained the call. Now the user has the guarantee that the call will have some effect. + +3. The IC is processing the call. For some calls this may be atomic, for others this involves multiple steps. + +4. Eventually, a response is produced and available in the (certified) [state tree](./index.md#state-tree-request-status) from which it can be retrieved for a certain amount of time. The response is either a `reply`, indicating success, or a `reject`, indicating some form of error. + +5. In case of high load on the IC, even if the call has not expired yet, the IC can forget the response data and only remember the call as `done`, to prevent a replay attack. + +6. Once the call's expiry time has passed, the IC can remove the call and its response from the (certified) [state tree](./index.md#state-tree-request-status) and thus completely forget about it. + +This yields the following interaction diagram: +```plantuml + (*) --> "User creates call" #DDDDDD + --> "Submitted to node\n(with 202 response)" as submit #DDDDDD + --> "received" + --> "processing" + if "" as X then + --> "replied" + --> "done" + else + --> "rejected (canister)" + --> "done" + + "X" --> "rejected (system)" + "received" --> "rejected (system)" + --> "done" + + "received" --> "pruned" #DDDDDD + "submit" --> "dropped" #DDDDDD + "done" --> "pruned" #DDDDDD + + endif +``` +State transitions may be instantaneous and not always externally visible. For example, the state of a request may move from `received` via `processing` to `replied` in one go. Similarly, the IC may not implement the `done` state at all, and keep calls in state `replied`/`rejected` until they are pruned. + +All gray states are *not* explicitly represented in the state of the IC, and are indistinguishable from "call does not exist". + +The characteristic property of the `received` state is that the call has made it past the (potentially malicious) endpoint *into the state of the IC*. It is now pointless (but harmless) to submit the (identical) call again. Before reaching that state, submitting the identical call to further nodes might be a useful safeguard against a malicious or misbehaving node. + +The characteristic property of the `processing` state is that *the initial effect of the call has happened or will happen*. This is best explained by an example: Consider a counter canister. It exports a method `inc` that increases the counter. Assume that the canister is bug free, and is not going to be forcibly removed. A user submits a call to call `inc`. If the user sees request status `processing`, the state change is guaranteed to happen. The user can stop monitoring the status and does not have to retry submitting. + +A call may be rejected by the IC or the canister. In either case, there is no guarantee about how much processing of the call has happened. + +To avoid replay attacks, the transition from `done` or `received` to `pruned` must happen no earlier than the call's `ingress_expiry` field. +If a subnet's time strictly exceeds the call's `ingress_expiry` field, the subnet's time exceeds the call's `ingress_expiry` field by at most 5 minutes, and the call's status is unknown to the IC (i.e., it was never in state `received`, `processing`, `replied`, `rejected`, or `done`), then the call will never be in one of these states. + +Calls should stay in `replied` or `rejected` for 5 minutes so that polling users can catch the response under good networking conditions +and low load on the IC. However, in case of high load on the IC, the IC can transition the call to `done` at any time. + +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](./index.md#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. +On the replica, a synchronous call request goes through the same states (`received`, `processing`, `replied`, `rejected`, or `done`) as the ones depicted for the [asynchronous API](#http-async-call-overview). +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 reduces 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` (deprecated) or `/api/v4/canister//call`. The request body consists of an authentication envelope with a `content` map with the following fields: + +- `request_type` (`text`): Always `call` + +- `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. + +- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). The canister will not start processing a call past its `ingress_expiry`. + +- `sender_info` (`map`, optional): Map with fields: + + - `info` (`blob`, required): The sender information passed to the canister. + + - `signer` (`blob`, required): The principal of the signing canister. This must be equal to the canister ID encoded in the `sender_pubkey`, i.e. the `signing_canister_id` component of the canister signature public key, as described in [canister signature](./index.md#canister-signatures). + + - `sig` (`blob`, required): Signature to authenticate the `info` field. This signature *must* be a [canister signature](./index.md#canister-signatures), using the 15 bytes `\x0Eic-sender-info` as the domain separator for the payload, and *must* verify using `sender_pubkey` as the canister signature public key. + +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](./index.md#cbor)) map with the fields specified below: + + - `status` (`text`): `"replied"` + + - `certificate` (`blob`): A certificate (see [Certification](./certification.md#certification)) with subtrees at `/request_status/` and `/time`, where `` is the [request ID](#request-id) of the update call. See [Request status](./index.md#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](./canister-interface.md#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. + +If the `certificate` includes a subnet delegation (see [Delegation](./certification.md#certification-delegation)), then + +- for requests to `/api/v3/canister//call`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at the path of the form `/subnet//canister_ranges`, + +- for requests to `/api/v4/canister//call`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at a path with prefix `/canister_ranges/`. + +This request type can *also* be used to call a query method (but not a composite query method). A user may choose to go this way, instead of via the faster and cheaper [Request: Query call](#http-query) below, if they want to get a *certified* response. Note that the canister state will not be changed by sending a call request type for a query method (except for transient state such as cycle balance, canister logs, and canister version). + +### 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` + +- `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 + +- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). The canister will not start processing a call past its `ingress_expiry`. + +- `sender_info` (`map`, optional): Map with fields: + + - `info` (`blob`, required): The sender information passed to the canister. + + - `signer` (`blob`, required): The principal of the signing canister. This must be equal to the canister ID encoded in the `sender_pubkey`, i.e. the `signing_canister_id` component of the canister signature public key, as described in [canister signature](./index.md#canister-signatures). + + - `sig` (`blob`, required): Signature to authenticate the `info` field. This signature *must* be a [canister signature](./index.md#canister-signatures), using the 15 bytes `\x0Eic-sender-info` as the domain separator for the payload, and *must* verify using `sender_pubkey` as the canister signature public key. + +The HTTP response to this request can have the following responses: + +- 202 HTTP status with empty body. Implying the request was accepted by the IC for further processing. Users should use [`read_state`](#http-read-state) to determine the status of the call. + +- 200 HTTP status with non-empty body. Implying an execution pre-processing error occurred. The body of the response contains more information about the IC specific error encountered. The body is a CBOR map with the following fields: + + - `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)). + +- 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 transient state such as cycle balance, canister logs, and canister version). + +:::note + +The functionality exposed via the [The IC management canister](./management-canister.md#ic-management-canister) can be used this way. + +::: + +### Request: Read state {#http-read-state} + +:::note + +Requesting paths with the prefix `/canister_ranges` and `/subnet` at `/api/v3/canister//read_state` might be deprecated in the future. Hence, users might want to point their requests for paths with the prefix `/canister_ranges` and `/subnet` to `/api/v3/subnet//read_state`. + +On the IC mainnet, the root subnet ID `tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe` can be used to retrieve the list of all IC mainnet's subnets by requesting the prefix `/subnet` at `/api/v3/subnet/tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe/read_state`. + +::: + +In order to read parts of the [The system state tree](./index.md#state-tree), the user makes a POST request to `/api/v2/canister//read_state` (deprecated), `/api/v2/subnet//read_state` (deprecated), `/api/v3/canister//read_state`, or `/api/v3/subnet//read_state`. The subnet form should be used when the information to be retrieved is subnet specific, i.e., when requesting paths with the prefix `/time`, `/canister_ranges`, or `/subnet`, and the subnet form must be used when requesting paths of the form `/subnet//metrics`. The request body consists of an authentication envelope with a `content` map with the following fields: + +- `request_type` (`text`): Always `read_state` + +- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). `ingress_expiry` refers to this request's expiry, not the expiry of any call request referenced in this read state request. + +- `paths` (sequence of paths): A list of at most 1000 paths, where a path is itself a sequence of at most 127 blobs. + +The HTTP response to this request can have the following forms: + +- 200 HTTP status with a non-empty body consisting of a CBOR (see [CBOR](./index.md#cbor)) map with the following fields: + + - `certificate` (`blob`): A certificate (see [Certification](./certification.md#certification)). + +- 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. + +In the following, we list properties of the returned certificate and specify conditions on the requested paths. + +If the `certificate` includes a subnet delegation (see [Delegation](./certification.md#certification-delegation)), then + +- for requests to `/api/v2/canister//read_state`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at the path of the form `/subnet//canister_ranges`, + +- for requests to `/api/v3/canister//read_state`, the `` must be included in a canister id range of the delegation's subnet id in the delegation's certificate at a path with prefix `/canister_ranges/`, + +- for requests to `/api/v2/subnet//read_state` and `/api/v3/subnet//read_state`, the `` must match the delegation's subnet id. + +The returned certificate reveals all values whose path has a requested path as a prefix except for + +- paths with prefix `/subnet//canister_ranges` which are only contained in the returned certificate + + - for requests to `/api/v2/canister//read_state` (deprecated) and `/api/v2/subnet//read_state` (deprecated); + + - if `` is the root subnet ID; + +- paths with prefix `/subnet//metrics` and `/subnet//node` which are only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to the value at a path with prefix `/canister_ranges/` in the state tree, or if `` matches ``. + +The returned certificate also always reveals `/time`, even if not explicitly requested. + +:::note + +The returned certificate might also reveal the SHA-256 hashes of values whose paths have not been requested +and whose paths might not even be allowed to be requested by the sender of the HTTP request. +This means that unauthorized users might obtain the SHA-256 hashes of ingress message responses +and private custom sections of the canister's module. +Hence, users are advised to use cryptographically strong nonces in their HTTP requests and +canister developers that aim at keeping data confidential are advised to add a secret cryptographic salt to their canister's responses and private custom sections. + +::: + +All requested paths must have the following form: + +- `/time`. Can always be requested. + +- `/api_boundary_nodes`, `/api_boundary_nodes/`, `/api_boundary_nodes//domain`, `/api_boundary_nodes//ipv4_address`, `/api_boundary_nodes//ipv6_address`. Can always be requested. + +- `/canister_ranges/`. Can only be requested at `/api/v2/subnet//read_state` and `/api/v3/subnet//read_state`. Cannot be requested at `/api/v2/canister//read_state` and `/api/v3/canister//read_state`. + +- `/subnet`, `/subnet/`, `/subnet//public_key`, `/subnet//type`, `/subnet//node`, `/subnet//node/`, `/subnet//node//public_key`. Can always be requested. + +- `/subnet//canister_ranges`, where `` is the root subnet ID. Can always be requested. + +- `/subnet//canister_ranges`, where `` is not the root subnet ID. Can be requested at `/api/v2/canister//read_state` and `/api/v2/subnet//read_state`. Cannot be requested at `/api/v3/canister//read_state` and `/api/v3/subnet//read_state`. + +- `/subnet//metrics`. Can only be requested at `/api/v2/subnet//read_state` and `/api/v3/subnet//read_state` (i.e., if `` matches ``). In particular, cannot be requested at `/api/v2/canister//read_state` and `/api/v3/canister//read_state`. + +- `/request_status/`, `/request_status//status`, `/request_status//reply`, `/request_status//reject_code`, `/request_status//reject_message`, `/request_status//error_code`. Can be requested if no path with such a prefix exists in the state tree or + + - the sender of the original request referenced by `` is the same as the sender of the read state request and + + - the effective canister id of the original request referenced by `` matches ``. + +- `/canister//module_hash`. Can be requested if `` matches ``. + +- `/canister//controllers`. Can be requested if `` matches ``. The order of controllers in the value at this path may vary depending on the implementation. + +- `/canister//metadata/`. Can be requested if `` matches ``, `` is encoded in UTF-8, and + + - canister with canister id `` does not exist or + + - canister with canister id `` is empty or + + - canister with canister id `` does not have `` as its custom section or + + - `` is a public custom section or + + - `` is a private custom section and the sender of the read state request is a controller of the canister. + +Moreover, + +- all paths with prefix `/request_status/` must refer to the same request ID ``; and + +- all paths with prefix `/canister_ranges/` must refer to the same subnet ID ``. + +If a path cannot be requested, then the HTTP response to the read state request is undefined. + +Note that the paths `/canister//certified_data` are not accessible with this method; these paths are only exposed to the canisters themselves via the System API (see [Certified data](./canister-interface.md#system-api-certified-data)). + +See [The system state tree](./index.md#state-tree) for details on the state tree. + +### Request: Query call {#http-query} + +A query call is a fast, but less secure way to call canister methods that do not change the canister state. +Only methods that are explicitly marked as "query methods" and "composite query methods" by the canister can be called this way. +In contrast to a query method, a composite query method can make further calls to query and composite query methods of canisters on the same subnet. + +The following limits apply to the evaluation of a query call: + +- The amount of cycles that are used in total (across all calls to query and composite query methods and their callbacks) during evaluation of a query call is at most `MAX_CYCLES_PER_QUERY`. + +- The maximum nesting level of calls during evaluation of a query call is at most `MAX_CALL_DEPTH_COMPOSITE_QUERY`. + +- The wall clock time spent on evaluation of a query call is at most `MAX_WALL_CLOCK_TIME_COMPOSITE_QUERY`. + +In order to make a query call to a canister, the user makes a POST request to `/api/v2/canister//query` (deprecated) or `/api/v3/canister//query`. The request body consists of an authentication envelope with a `content` map with the following fields: + +- `request_type` (`text`): Always `"query"`. + +- `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. + +- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication). + +- `sender_info` (`map`, optional): Map with fields: + + - `info` (`blob`, required): The sender information passed to the canister. + + - `signer` (`blob`, required): The principal of the signing canister. This must be equal to the canister ID encoded in the `sender_pubkey`, i.e. the `signing_canister_id` component of the canister signature public key, as described in [canister signature](./index.md#canister-signatures). + + - `sig` (`blob`, required): Signature to authenticate the `info` field. This signature *must* be a [canister signature](./index.md#canister-signatures), using the 15 bytes `\x0Eic-sender-info` as the domain separator for the payload, and *must* verify using `sender_pubkey` as the canister signature public key. + +The HTTP response to this request can have the following forms: + +- 200 HTTP status with a non-empty body consisting of a CBOR (see [CBOR](./index.md#cbor)) map with the following fields: + + - `status` (`text`): `"replied"` + + - `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data. + + - `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response. + +- 200 HTTP status with a non-empty body consisting of a CBOR (see [CBOR](./index.md#cbor)) map with the following fields: + + - `status` (`text`): `"rejected"` + + - `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)). + + - `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response. + +- 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. + +:::note + +Although `signatures` only contains one node signature, we still declare its type to be a list to prevent future breaking changes +if we include more signatures in a future version of the protocol specification. + +::: + +A successful response to a query call (200 HTTP status) contains a list with one signature for the returned response produced by the IC node that evaluated the query call. The signature (whose type is denoted as `node-signature`) is a CBOR (see [CBOR](./index.md#cbor)) map with the following fields: + +- `timestamp` (`nat`): the timestamp of the signature. + +- `signature` (`blob`): the actual signature. + +- `identity` (`principal`): the principal of the node producing the signature. + +Given a query (the `content` map from the request body) `Q`, a response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state`, the following predicate describes when the returned response `R` is correctly signed: + +``` +verify_response(Q, R, Cert) + = verify_cert(Cert) ∧ + ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ + (Cert.delegation ≠ NoDelegation ∧ SubnetId = Cert.delegation.subnet_id ∧ lookup*(["canister_ranges",SubnetId], Cert.delegation.certificate) = Ranges)) ∧ + effective_canister_id ∈ Ranges ∧ + ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. + lookup(["subnet",SubnetId,"node",NodeId,"public_key"], Cert) = Found PK ∧ + if R.status = "replied" then + verify_signature PK Sig ("\x0Bic-response" · hash_of_map({ + status: "replied", + reply: R.reply, + timestamp: T, + request_id: hash_of_map(Q)})) + else + verify_signature PK Sig ("\x0Bic-response" · hash_of_map({ + status: "rejected", + reject_code: R.reject_code, + reject_message: R.reject_message, + error_code: R.error_code, + timestamp: T, + request_id: hash_of_map(Q)})) +``` + +where `RootSubnetId` is the a priori known principal of the root subnet. Moreover, all timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation must be "recent enough". + +:::note + +This specification leaves it up to the client to define expiry times for the timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation. A reasonable expiry time for timestamps in `R.signatures` and the certificate `Cert` is 5 minutes (analogously to the maximum allowed ingress expiry enforced by the IC mainnet). Delegations require expiry times of at least a week since the IC mainnet refreshes the delegations only after replica upgrades which typically happen once a week. + +::: + +### Effective canister id {#http-effective-canister-id} + +The `` in the URL paths of requests is the *effective* destination of the request. +It must be contained in the canister ranges of a subnet, otherwise the corresponding HTTP request is rejected. + +- If the request is an update call to the Management Canister (`aaaaa-aa`), then: + + - If the call is to the `create_canister` or `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. + +- If the request is a query call to the Management Canister (`aaaaa-aa`), then: + + - If the call is to the `list_canisters` method, then any principal can be used as the effective canister id for this call. + + - 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. + +- If the request is an update or query call to a canister that is not the Management Canister (`aaaaa-aa`), then the effective canister id must be the `canister_id` in the request. + +:::note + +The expectation is that user-side agent code shields users and developers from the notion of effective canister id, in analogy to how the System API interface shields canister developers from worrying about routing. + +The Internet Computer blockchain mainnet does not support `provisional_create_canister_with_cycles` and thus all calls to this method are rejected independently of the effective canister id. + +In development instances of the Internet Computer Protocol (e.g. testnets), the effective canister id of a request submitted to a node must be a canister id from the canister ranges of the subnet to which the node belongs. + +::: + +### Authentication {#authentication} + +All requests coming in via the HTTPS interface need to be either *anonymous* or *authenticated* using a cryptographic signature. To that end, the following fields are present in the `content` map in all cases: + +- `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()](./canister-interface.md#system-api-time)). This avoids replay attacks: The IC will not accept requests, or transition call 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. The acceptance 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. Note that the `ingress_expiry` of a `read_state` request is independent of the `ingress_expiry` of an earlier `call` request, they do *not* need to be the same. + +- `sender` (`Principal`, required): The user who issued the request. + +The envelope, i.e. the overall request, has the following keys: + +- `content` (`record`): the actual request content + +- `sender_pubkey` (`blob`, optional): Public key used to authenticate this request. Since a user may in the future have more than one key, this field tells the IC which key is used. + +- `sender_delegation` (`array` of maps, optional): a chain of delegations, starting with the one signed by `sender_pubkey` and ending with the one delegating to the key relating to `sender_sig`. Every public key in the chain of delegations should appear exactly once: cycles (a public key delegates to another public key that already previously appeared in the chain) or self-signed delegations (a public key delegates to itself) are not allowed and such requests will be refused by the IC. + +- `sender_sig` (`blob`, optional): Signature to authenticate this request. + +The public key must authenticate the `sender` principal: + +- A public key can authenticate a principal if the latter is a self-authenticating id derived from that public key (see [Special forms of Principals](./index.md#id-classes)). + +- The fields `sender_pubkey`, `sender_sig`, and `sender_delegation` must be omitted if the `sender` field is the anonymous principal. The fields `sender_pubkey` and `sender_sig` must be set if the `sender` field is not the anonymous principal. + +The request id (see [Request ids](#request-id)) is calculated from the content record. This allows the signature to be based on the request id, and implies that signature and public key are not semantically relevant. + +The field `sender_pubkey` contains a public key supported by one of the schemes described in [Signatures](./index.md#signatures). + +Signing transactions can be delegated from one key to another one. If delegation is used, then the `sender_delegation` field contains an array of delegations, each of which is a map with the following fields: + +- `delegation` (`map`): Map with fields: + + - `pubkey` (`blob`): Public key as described in [Signatures](./index.md#signatures). + + - `expiration` (`nat`): Expiration of the delegation, in nanoseconds since 1970-01-01, analogously to the `ingress_expiry` field above. + + - `targets` (`array` of `CanisterId`, optional): If this field is set, the delegation only applies for requests sent to the canisters in the list. The list must contain no more than 1000 elements; otherwise, the request will not be accepted by the IC. + +- `signature` (`blob`): Signature on the 32-byte [representation-independent hash](#hash-of-map) of the map contained in the `delegation` field as described in [Signatures](./index.md#signatures), using the 27 bytes `\x1Aic-request-auth-delegation` as the domain separator. + + For the first delegation in the array, this signature is created with the key corresponding to the public key from the `sender_pubkey` field, all subsequent delegations are signed with the key corresponding to the public key contained in the preceding delegation. + +The `sender_sig` field is calculated by signing the concatenation of the 11 bytes `\x0Aic-request` (the domain separator) and the 32 byte [request id](#request-id) with the secret key that belongs to the key specified in the last delegation or, if no delegations are present, the public key specified in `sender_pubkey`. + +The delegation field, if present, must not contain more than 20 delegations. + +### Representation-independent hashing of structured data {#hash-of-map} + +Structured data, such as (recursive) maps, are authenticated by signing a representation-independent hash of the data. This hash is computed as follows (using SHA256 in the steps below): + +1. For each field that is present in the map (i.e. omitted optional fields are indeed omitted): + + - concatenate the hash of the field's name (in ascii-encoding, without terminal `\x00`) and the hash of the value (as specified below). + +2. Sort these concatenations from low to high. + +3. Concatenate the sorted elements, and hash the result. + +The resulting hash of length 256 bits (32 bytes) is the representation-independent hash. + +Field values are hashed as follows: + +- Binary blobs (`canister_id`, `arg`, `nonce`, `module`) are hashed as-is. + +- Strings (`request_type`, `method_name`) are hashed by hashing their binary encoding in UTF-8, without a terminal `\x00`. + +- Natural numbers (`compute_allocation`, `memory_allocation`, `ingress_expiry`) are hashed by hashing their binary encoding using the shortest form [Unsigned LEB128](https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128) encoding. For example, `0` should be encoded as a single zero byte `[0x00]` and `624485` should be encoded as byte sequence `[0xE5, 0x8E, 0x26]`. + +- Integers are hashed by hashing their encoding using the shortest form [Signed LEB128](https://en.wikipedia.org/wiki/LEB128#Signed_LEB128) encoding. For example, `0` should be encoded as a single zero byte `[0x00]` and `-123456` should be encoded as byte sequence `[0xC0, 0xBB, 0x78]`. + +- Arrays (`paths`) are hashed by hashing the concatenation of the hashes of the array elements. + +- Maps (`sender_delegation`) are hashed by recursively computing their representation-independent hash. + +:::tip + +Example calculation (where `H` denotes SHA-256 and `·` denotes blob concatenation) of a representation independent hash +for a map with a nested map in a field value: +``` +hash_of_map({ "reply": { "arg": "DIDL\x00\x00" } }) + = H(concat (sort [ H("reply") · hash_of_map({ "arg": "DIDL\x00\x00" }) ])) + = H(concat (sort [ H("reply") · H(concat (sort [ H("arg") · H("DIDL\x00\x00") ])) ])) +``` + +::: + +### Request ids {#request-id} + +When signing requests or querying the status of a request (see [Request status](./index.md#state-tree-request-status)) in the state tree, the user identifies the request using a *request id*, which is the [representation-independent hash](#hash-of-map) of the `content` map of the original request. A request id must have length of 32 bytes. + +:::note + +The request id is independent of the representation of the request (currently only CBOR, see [CBOR](./index.md#cbor)), and does not change if the specification adds further optional fields to a request type. + +::: + +:::note + +The recommended textual representation of a request id is a hexadecimal string with lower-case letters prefixed with '0x'. E.g., request id consisting of bytes `[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, 1C, 1D, 1E, 1F]` should be displayed as `0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f`. + +::: + +:::tip + +Example calculation (where `H` denotes SHA-256 and `·` denotes blob concatenation) in which we assume that the optional nonce is not provided and thus omitted: +``` +hash_of_map({ request_type: "call", sender: 0x04, ingress_expiry: 1685570400000000000, canister_id: 0x00000000000004D2, method_name: "hello", arg: "DIDL\x00\xFD*"}) + = H(concat (sort + [ H("request_type") · H("call") + , H("sender") · H("0x04") + , H("ingress_expiry") · H(1685570400000000000) + , H("canister_id") · H("\x00\x00\x00\x00\x00\x00\x04\xD2") + , H("method_name") · H("hello") + , H("arg") · H("DIDL\x00\xFD*") + ])) + = H(concat (sort + [ 769e6f87bdda39c859642b74ce9763cdd37cb1cd672733e8c54efaa33ab78af9 · 7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed + , 0a367b92cf0b037dfd89960ee832d56f7fc151681bb41e53690e776f5786998a · e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71 + , 26cec6b6a9248a96ab24305b61b9d27e203af14a580a5b1ff2f67575cab4a868 · db8e57abc8cda1525d45fdd2637af091bc1f28b35819a40df71517d1501f2c76 + , 0a3eb2ba16702a387e6321066dd952db7a31f9b5cc92981e0a92dd56802d3df9 · 4d8c47c3c1c837964011441882d745f7e92d10a40cef0520447c63029eafe396 + , 293536232cf9231c86002f4ee293176a0179c002daa9fc24be9bb51acdd642b6 · 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 + , b25f03dedd69be07f356a06fe35c1b0ddc0de77dcd9066c4be0c6bbde14b23ff · 6c0b2ae49718f6995c02ac5700c9c789d7b7862a0d53e6d40a73f1fcd2f70189 + ])) + = H(concat + [ 0a367b92cf0b037dfd89960ee832d56f7fc151681bb41e53690e776f5786998a · e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71 + , 0a3eb2ba16702a387e6321066dd952db7a31f9b5cc92981e0a92dd56802d3df9 · 4d8c47c3c1c837964011441882d745f7e92d10a40cef0520447c63029eafe396 + , 26cec6b6a9248a96ab24305b61b9d27e203af14a580a5b1ff2f67575cab4a868 · db8e57abc8cda1525d45fdd2637af091bc1f28b35819a40df71517d1501f2c76 + , 293536232cf9231c86002f4ee293176a0179c002daa9fc24be9bb51acdd642b6 · 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 + , 769e6f87bdda39c859642b74ce9763cdd37cb1cd672733e8c54efaa33ab78af9 · 7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed + , b25f03dedd69be07f356a06fe35c1b0ddc0de77dcd9066c4be0c6bbde14b23ff · 6c0b2ae49718f6995c02ac5700c9c789d7b7862a0d53e6d40a73f1fcd2f70189 + ]) + = 1d1091364d6bb8a6c16b203ee75467d59ead468f523eb058880ae8ec80e2b101 +``` + +::: + +### Reject codes {#reject-codes} + +An API request or inter-canister call that is pending in the IC will eventually result in either a *reply* (indicating success, and carrying data) or a *reject* (indicating an error of some sorts). A reject contains a *reject code* that classifies the error and a hopefully helpful *reject message* string. + +Reject codes are member of the following enumeration: + +- `SYS_FATAL` (1): Fatal system error, retry unlikely to be useful. + +- `SYS_TRANSIENT` (2): Transient system error, retry might be possible. + +- `DESTINATION_INVALID` (3): Invalid destination (e.g. canister/account does not exist) + +- `CANISTER_REJECT` (4): Explicit reject by the canister. + +- `CANISTER_ERROR` (5): Canister error (e.g., trap, no response) + +- `SYS_UNKNOWN` (6): Response unknown; system stopped waiting for it (e.g., timed out, or system under high load). This code is only applicable to inter-canister calls that used `ic0.call_with_best_effort_response`. + +The symbolic names of this enumeration are used throughout this specification, but on all interfaces (HTTPS API, System API), they are represented as positive numbers as given in the list above. + +The error message is guaranteed to be a string, i.e. not arbitrary binary data. + +When canisters explicitly reject a message (see [Public methods](./canister-interface.md#system-api-requests)), they can specify the reject message, but *not* the reject code; it is always `CANISTER_REJECT`. In this sense, the reject code is trustworthy: the reject code is always fixed by the protocol, i.e., the canister cannot freely specify the reject code. + +### Error codes {#error-codes} + +Implementations of the API can provide additional details for rejected messages in the form of a textual label identifying the error condition. API clients can use these labels to handle errors programmatically or suggest recovery paths to the user. The specification reserves error codes matching the regular expression `IC[0-9]+` (e.g., `IC502`) for the DFINITY implementation of the API. + +### Status endpoint {#api-status} + +Additionally, the Internet Computer provides an API endpoint to obtain various status fields at + + /api/v2/status + +For this endpoint, the user performs a GET request, and receives a CBOR (see [CBOR](./index.md#cbor)) value with the following fields. The IC may include additional implementation-specific fields. + +- `root_key` (blob, optional): The public key (a DER-encoded BLS key) of the root key of this instance of the Internet Computer Protocol. This *must* be present in short-lived development instances, to allow the agent to fetch the public key. For the Internet Computer, agents must have an independent trustworthy source for this data (e.g., the system API `ic0.root_key_size` and `ic0.root_key_copy`), and must not be tempted to fetch it from this insecure location. + +See [CBOR encoding of requests and responses](#api-cbor) for details on the precise CBOR encoding of this object. + +:::note + +Future additions may include local time, geographic location, and other useful implementation-specific information such as blockheight. This data may possibly be signed by the node. + +::: + +### CBOR encoding of requests and responses {#api-cbor} + +Requests and responses are specified here as records with named fields and using suggestive human readable syntax. The actual format in the body of the HTTP request or response, however, is CBOR (see [CBOR](./index.md#cbor)). + +Concretely, it consists of a data item with major type 6 ("Semantic tag") and tag value `55799`, followed by a record. + +Requests consist of an envelope record with keys `sender_sig` (a blob), `sender_pubkey` (a blob) and `content` (a record). The first two are metadata that are used for request authentication, while the last one is the actual content of the request. + +The following encodings are used: + +- Strings: Major type 3 ("Text string"). + +- Blobs: Major type 2 ("Byte string"). + +- Nats: Major type 0 ("Unsigned integer") if small enough to fit that type, else the [Bignum](https://www.rfc-editor.org/rfc/rfc8949#name-bignums) format is used. + +- Records: Major type 5 ("Map of pairs of data items"), followed by the fields, where keys are encoded with major type 3 ("Text string"). + +- Arrays: Major type 4 ("Array of data items"). + +As advised by [section "Creating CBOR-Based Protocols"](https://www.rfc-editor.org/rfc/rfc8949#name-creating-cbor-based-protoco) of the CBOR spec, we clarify that: + +- Floating-point numbers may not be used to encode integers. + +- Duplicate keys are prohibited in CBOR maps. + +:::tip + +A typical request would be (written in [CBOR diagnostic notation](https://www.rfc-editor.org/rfc/rfc8949#name-diagnostic-notation), which can be checked and converted on [cbor.me](https://cbor.me/)): +``` +55799({ + "content": { + "request_type": "call", + "canister_id": h'ABCD01', + "method_name": "say_hello", + "arg": h'0061736d01000000' + }, + "sender_sig": h'DEADBEEF', + "sender_pubkey": h'b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde' +}) +``` + +::: + +### CDDL description of requests and responses {#api-cddl} + +This section summarizes the format of the CBOR data passed to and from the entry points described above. You can also [download the file](_attachments/requests.cddl) and see [CDDL](./index.md#cddl) for more information. + +### Ordering guarantees + +The order in which the various messages between canisters are delivered and executed is not fully specified. The guarantee provided by the IC is that if a canister sends two messages to a canister and they both start being executed by the receiving canister, then they do so in the order in which the messages were sent. + +More precisely: + +- Messages between any *two* canisters, if delivered to the canister, start executing in order. Note that message delivery can fail for arbitrary reasons (e.g., high system load). + +- If a WebAssembly function, within a single invocation, makes multiple calls to the same canister, they are queued in the order of invocations to `ic0.call_perform`. + +- Responses (including replies with `ic0.msg_reply`, explicit rejects with `ic0.msg_reject` and system-generated error responses) do *not* have any ordering guarantee relative to each other or to method calls. + +- There is no particular order guarantee for ingress messages submitted via the HTTPS interface. + +### Synchronicity across nodes + +This document describes the Internet Computer as having a single global state that can be modified and queried. In reality, it consists of many nodes, which may not be perfectly in sync. + +As long as you talk to one (honest) node only, the observed behavior is nicely sequential. If you issue an update (i.e. state-mutating) call to a canister (e.g. bump a counter), and node A indicates that the call has been executed, and you then issue a query call to node A, then A's response is guaranteed to include the effect of the update call (and you will receive the updated counter value). + +If you then (quickly) issue a read request to node B, it may be that B responds to your read query based on the old state of the canister (and you might receive the old counter value). + +A related problem is that query calls are not certified, and nodes may be dishonest in their response. In that case, the user might want to get more assurance by querying multiple nodes and comparing the result. However, it is (currently) not possible to query a *specific* state. + +:::note + +Applications can work around these problems. For the first problem, the query result could be such that the user can tell if the update has been received or not. For the second problem, even if using [certified data](./canister-interface.md#system-api-certified-data) is not possible, if replies are monotonic in some sense the user can get assurance in their intersection (e.g. if the query returns a list of events that grows over time, then even if different nodes return different lists, the user can get assurance in those events that are reported by many nodes). + +::: + + diff --git a/docs/references/ic-interface-spec/index.md b/docs/references/ic-interface-spec/index.md new file mode 100644 index 0000000..232b316 --- /dev/null +++ b/docs/references/ic-interface-spec/index.md @@ -0,0 +1,632 @@ +--- +title: "IC Interface Specification" +description: "Introduction, pervasive concepts, and the IC system state tree" +sidebar: + label: "Introduction" + order: 0 +--- + +## Introduction + +Welcome to *the Internet Computer*! We speak of "the" Internet Computer, because although under the hood a large number of physical computers are working together in a blockchain protocol, in the end we have the appearance of a single, shared, secure and world-wide accessible computer. Developers who want to build decentralized applications (or *dapps* for short) that run on the Internet Computer blockchain and end-users who want to use those dapps need to know very little, if anything, about the underlying protocol. However, knowing some details about the interfaces that the Internet Computer exposes can allow interested developers and architects to take fuller advantages of the unique features that the Internet Computer provides. + +### Target audience + +This document describes this *external* view of the Internet Computer, i.e. the low-level interfaces it provides to dapp developers and users, and what will happen when they use these interfaces. + +:::note + +While this document describes the external interface and behavior of the Internet Computer, it is not intended as end-user or end-developer documentation. Most developers will interact with the Internet Computer through additional tooling like the SDK, Canister Development Kits and Motoko. Please see the [developer docs](/) for suitable documentation. + +::: + +The target audience of this document are + +- those who use these low-level interfaces (e.g. implement agents, canister developments kits, emulators, other tooling). + +- those who implement these low-level interfaces (e.g. developers of the Internet Computer implementation) + +- those who want to understand the intricacies of the Internet Computer's behavior in great detail (e.g. to do a security analysis) + +:::note + +This document is a rigorous, technically dense reference. It is not an introduction to the Internet Computer, and as such most useful to those who understand the high-level concepts. Please see more high-level documentation first. + +::: + +### Scope of this document + +If you think of the Internet Computer as a distributed engine that executes WebAssembly-based dapps, then this document describes exclusively the aspect of executing those dapps. To the extent possible, this document will *not* talk about consensus protocols, nodes, subnets, orthogonal persistence or governance. + +This document tries to be implementation agnostic: It would apply just as well to a (hypothetical) compatible reimplementation of the Internet Computer. This implies that this document does not cover interfaces towards those running the Internet Computer (e.g. data center operators, protocol developers, governance users), as topics like node update, monitoring, logging are inherently tied to the actual *implementation* and its architecture. + +### Overview of the Internet Computer + +Dapps on the Internet Computer, or *IC* for short, are implemented as *canister smart contracts*, or *canisters* for short. If you want to build on the Internet Computer as a dapp developer, you first create a *canister module* that contains the WebAssembly code and configuration for your dapp, and deploy it using the [HTTPS interface](./https-interface.md#http-interface). You can create canister modules using the Motoko language and the SDK, which is more convenient. If you want to use your own tooling, however, then this document describes [what a canister module looks like](./canister-interface.md#canister-module-format) and how the [WebAssembly code can interact with the IC](./canister-interface.md#system-api). + +Once your dapp is running on the Internet Computer, it is a canister smart contract, and users can interact with it. They can use the [HTTPS interface](./https-interface.md#http-interface) to interact with the canister according to the [System API](./canister-interface.md#system-api). + +The user can also use the HTTPS interface to issue read-only queries, which are faster, but cannot change the state of a canister. + +```plantuml + actor Developer + actor User + participant "Internet Computer" as IC + participant "Canister 1" as Can1 + Developer -> IC : /submit create canister + create Can1 + IC -> Can1 : create + Developer <-- IC : canister-id=1 + Developer -> IC : /submit install module + IC -> Can1 : initialize + ||| + User -> IC : /submit call "hello" + IC -> Can1 : hello + return "Hello world!" + User <-- IC : "Hello World!" +``` +**A typical use of the Internet Computer. (This is a simplified view; some of the arrows represent multiple interaction steps or polling.)** + +Sections "[HTTPS Interface](./https-interface.md#http-interface)" and "[Canister interface (System API)](./canister-interface.md#system-api)" describe these interfaces, together with a brief description of what they do. Afterwards, you will find a [more formal description](./abstract-behavior.md#abstract-behavior) of the Internet Computer that describes its abstract behavior with more rigor. + +### Nomenclature + +To get some consistency in this document, we try to use the following terms with precision: + +We avoid the term "client", as it could be the client of the Internet Computer or the client inside the distributed network that makes up the Internet Computer. Instead, we use the term *user* to denote the external entity interacting with the Internet Computer, even if in most cases it will be some code (sometimes called "agent") acting on behalf of a (human) user. + +The public entry points of canisters are called *methods*. Methods can be declared to be either *update methods* (state mutation is preserved, can call update and query methods of arbitrary canisters), *query methods* (state mutation is discarded, no further calls can be made), or *composite query* methods (state mutation is discarded, can call query and composite query methods of canisters on the same subnet). + +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. +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. + +WebAssembly *functions* are exported by the WebAssembly module or provided by the System API. These are *invoked* and can either *trap* or *return*, possibly with a return value. A trap is caused by an irrecoverable error in the WebAssembly module (e.g., division by zero) or System API execution (e.g., running out of memory or exceeding the instruction limit for a single message execution imposed by the Internet Computer). Functions, too, have parameters and take arguments. + +External *users* interact with the Internet Computer by issuing *requests* on the HTTPS interface. Requests have responses which can either be replies or rejects. Some requests cause internal messages to be created. + +Canisters and users are identified by a *principal*, sometimes also called an *id*. + +## Pervasive concepts + +Before going into the details of the four public interfaces described in this document (namely the agent-facing [HTTPS interface](./https-interface.md#http-interface), the canister-facing [System API](./canister-interface.md#system-api), the [virtual Management canister](./management-canister.md#ic-management-canister) and the [System State Tree](#state-tree)), this section introduces some concepts that transcend multiple interfaces. + +### Unspecified constants and limits + +This specification may refer to certain constants and limits without specifying their concrete value (yet), i.e. they are implementation defined. Many are resource limits which are relevant only to specify the error-handling behavior of the IC (which, as mentioned above, is also not yet precisely described in this document). This list is not complete. + +- `MAX_CYCLES_PER_MESSAGE` + + Amount of cycles that a canister has to have before a message is attempted to be executed, which is deducted from the canister balance before message execution. See [Message execution](./abstract-behavior.md#rule-message-execution). + +- `MAX_CYCLES_PER_RESPONSE` + + Amount of cycles that the IC sets aside when a canister performs a call. This is used to pay for processing the response message, and unused cycles after the execution of the response are refunded. See [Message execution](./abstract-behavior.md#rule-message-execution). + +- `MAX_CYCLES_PER_QUERY` + + Maximum amount of cycles that can be used in total (across all calls to query and composite query methods and their callbacks) during evaluation of a query call. + +- `CHUNK_STORE_SIZE` + + 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. + +- `DEFAULT_PROVISIONAL_CYCLES_BALANCE` + + Amount of cycles allocated to a new canister by default, if not explicitly specified. See [IC method](./management-canister.md#ic-provisional_create_canister_with_cycles). + +- `MAX_CALL_DEPTH_COMPOSITE_QUERY` + + Maximum nesting level of calls during evaluation of a query call to a composite query method. + +- `MAX_WALL_CLOCK_TIME_COMPOSITE_QUERY` + + Maximum wall clock time spent on evaluation of a query call. + +- `MAX_SNAPSHOTS` + + Maximum number of canister snapshots per canister. + +- `MAX_CALL_TIMEOUT` + + The maximum timeout (in seconds) for an inter-canister call. + +- `MAX_ENV_VAR_NAME_LENGTH` + + The maximum length of an environment variable name. + +- `MAX_ENV_VAR_VALUE_LENGTH` + + The maximum length of an environment variable value. + +- `MAX_ENV_VAR_COUNT` + + The maximum number of environment variables allowed. + +### Principals {#principal} + +Principals are generic identifiers for canisters, users and possibly other concepts in the future. As far as most uses of the IC are concerned they are *opaque* binary blobs with a length between 0 and 29 bytes, and there is intentionally no mechanism to tell canister ids and user ids apart. + +There is, however, some structure to them to encode specific authentication and authorization behavior. + +#### Special forms of Principals {#id-classes} + +In this section, `H` denotes SHA-224, `·` denotes blob concatenation and `|p|` denotes the length of `p` in bytes, encoded as a single byte. + +There are several classes of ids: + +1. *Opaque ids*. + + These are always generated by the IC and have no structure of interest outside of it. + +:::note + +Typically, these end with the byte `0x01`, but users of the IC should not need to care about that. + +::: + +2. *Self-authenticating ids*. + + These have the form `H(public_key) · 0x02` (29 bytes). + + An external user can use these ids as the `sender` of a request if they own the corresponding private key. The public key uses one of the encodings described in [Signatures](#signatures). + +3. *Derived ids* + + These have the form `H(|registering_principal| · registering_principal · derivation_nonce) · 0x03` (29 bytes). + + These ids are treated specially when an id needs to be registered. In such a request, whoever requests an id can provide a `derivation_nonce`. By hashing that together with the principal of the caller, every principal has a space of ids that only they can register ids from. + +:::note + +Derived IDs are currently not explicitly used in this document, but they may be used internally or in the future. + +::: + +4. *Anonymous id* + + This has the form `0x04`, and is used for the anonymous caller. It can be used in call and query requests without a signature. + +5. *Reserved ids* + + These have the form of `blob · 0x7f`, `0 ≤ |blob| < 29`. + + These ids can be useful for applications that want to re-use the [Textual representation of principals](#textual-ids) but want to indicate explicitly that the blob does not address any canisters or a user. + +When the IC creates a *fresh* id, it never creates a self-authenticating id, reserved id, an anonymous id or an id derived from what could be a canister or user. + +#### Textual representation of principals {#textual-ids} + +We specify a *canonical textual format* that is recommended whenever principals need to be printed or read in textual format, e.g. in log messages, transactions browser, command line tools, source code. + +The textual representation of a blob `b` is `Grouped(Base32(CRC32(b) · b))` where + +- `CRC32` is a four byte check sequence, calculated as defined by ISO 3309, ITU-T V.42, and [elsewhere](https://www.w3.org/TR/2003/REC-PNG-20031110/#5CRC-algorithm), and stored as big-endian, i.e., the most significant byte comes first and then the less significant bytes come in descending order of significance (MSB B2 B1 LSB). + +- `Base32` is the Base32 encoding as defined in [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648#section-6), with no padding character added. + +- The middle dot denotes concatenation. + +- `Grouped` takes an ASCII string and inserts the separator `-` (dash) every 5 characters. The last group may contain less than 5 characters. A separator never appears at the beginning or end. + +The textual representation is conventionally printed with *lower case letters*, but parsed case-insensitively. + +Because the maximum size of a principal is 29 bytes, the textual representation will be no longer than 63 characters (10 times 5 plus 3 characters with 10 separators in between them). + +:::tip + +The canister with id `0xABCD01` has check sequence `0x233FF206` ([online calculator](https://crccalc.com/?crc=ABCD01&method=crc32&datatype=hex&outtype=hex)); the final id is thus `em77e-bvlzu-aq`. + +Example encoding from hex, and decoding to hex, in bash (the following can be pasted into a terminal as is): +``` +function textual_encode() { + ( echo "$1" | xxd -r -p | /usr/bin/crc32 /dev/stdin; echo -n "$1" ) | + xxd -r -p | base32 | tr A-Z a-z | + tr -d = | fold -w5 | paste -sd'-' - +} + +function textual_decode() { + echo -n "$1" | tr -d - | tr a-z A-Z | + fold -w 8 | xargs -n1 printf '%-8s' | tr ' ' = | + base32 -d | xxd -p | tr -d '\n' | cut -b9- | tr a-z A-Z +} +``` + +::: + +### Canister lifecycle {#canister-lifecycle} + +Dapps on the Internet Computer are called *canisters*. Conceptually, they consist of the following pieces of state: + +- A canister id (a [principal](#principal)) + +- Their *controllers* (a possibly empty list of [principal](#principal)) + +- A cycle balance + +- A reserved cycles balance, which are cycles set aside from the main cycle balance for resource payments. + +- The *canister status*, which is one of `running`, `stopping` or `stopped`. + +- Resource reservations + +A canister can be *empty* (e.g. directly after creation) or *non-empty*. A non-empty canister also has + +- code, in the form of a canister module + +- memories (heap and stable memory) + +- globals + +- possibly further data that is specific to the implementation of the IC (e.g. queues) + +Canisters are empty after creation and uninstallation, and become non-empty through [code installation](./management-canister.md#ic-install_code). + +If an empty canister receives a response, that response is dropped, as if the canister trapped when processing the response. The cycles set aside for its processing and the cycles carried on the responses are added to the canister's *cycles* balance. + +#### Canister cycles {#canister-cycles} + +The IC relies on *cycles*, a utility token, to manage its resources. A canister pays for the resources it uses from its *cycle balances*. A *cycle\_balance* is stored as 128-bit unsigned integers and operations on them are saturating. In particular, if *cycles* are added to a canister that would bring its main cycle balance beyond 2128-1, then the balance will be capped at 2128-1 and any additional cycles will be lost. + +When both the main and the reserved cycles balances of a canister fall to zero, the canister is *deallocated*. This has the same effect as + +- uninstalling the canister (as described in [IC method](./management-canister.md#ic-uninstall_code)) + +- setting all resource reservations to zero + +Afterwards the canister is empty. It can be reinstalled after topping up its main balance. + +:::note + +Once the IC frees the resources of a canister, its id, *cycle* balances, *controllers*, canister *version*, and the total number of canister changes are preserved on the IC for a minimum of 10 years. What happens to the canister after this period is currently unspecified. + +::: + +#### Canister status {#canister-status} + +The canister status can be used to control whether the canister is processing calls: + +- In status `running`, calls to the canister are processed as normal. + +- In status `stopping`, calls to the canister are rejected by the IC with reject code `CANISTER_ERROR` (5), but responses to the canister are processed as normal. + +- In status `stopped`, calls to the canister are rejected by the IC with reject code `CANISTER_ERROR` (5), and there are no outstanding responses. + +In all cases, calls to the [management canister](./management-canister.md#ic-management-canister) are processed, regardless of the state of the managed canister. + +The controllers of the canister or subnet admins can initiate transitions between these states using [`stop_canister`](./management-canister.md#ic-stop_canister) and [`start_canister`](./management-canister.md#ic-start_canister), and query the state using [`canister_status`](./management-canister.md#ic-canister_status) (NB: this call returns additional information, such as the cycle balance of the canister). The canister itself can also query its state using [`ic0.canister_status`](./canister-interface.md#system-api-canister-status). + +:::note + +This status is orthogonal to whether a canister is empty or not: an empty canister can be in status `running`. Calls to such a canister are still rejected by the IC, but because the canister is empty. + +::: + +:::note + +This status is orthogonal to whether a canister is frozen or not: a frozen canister can be in status `running`. Calls to such a canister are still rejected by the IC, but because the canister is frozen, the returned reject code is `SYS_TRANSIENT`. + +::: + +:::note + +If a canister is in the `stopped` state, an additional boolean may be of interest: `ready_for_migration` indicates whether a stopped canister is ready to be migrated to another subnet (i.e., whether it has empty queues and flushed streams). This flag can only ever be `true` if the `status` is `stopped`. This property is guaranteed by the protocol, but deliberately not on the type level in order to facilitate backwards compatible service evolution. + +::: + +### Signatures {#signatures} + +Digital signature schemes are used for authenticating messages in various parts of the IC infrastructure. Signatures are domain separated, which means that every message is prefixed with a byte string that is unique to the purpose of the signature. + +The IC supports multiple signature schemes, with details given in the following subsections. For each scheme, we specify the data encoded in the public key (which is always DER-encoded, and indicates the scheme to use) as well as the form of the signatures (which are opaque blobs for the purposes of the rest of this specification). + +In all cases, the signed *payload* is the concatenation of the domain separator and the message. All uses of signatures in this specification indicate a domain separator, to uniquely identify the purpose of the signature. The domain separators are prefix-free by construction, as their first byte indicates their length. + +#### Ed25519 and ECDSA signatures {#ecdsa} + +Plain signatures are supported for the schemes + +- [**Ed25519**](https://ed25519.cr.yp.to/index.html) or + +- [**ECDSA**](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) on curve P-256 (also known as `secp256r1`), using SHA-256 as hash function, as well as on the Koblitz curve `secp256k1`. + +- Public keys must be valid for signature schemes Ed25519 or ECDSA and are encoded as DER. + + - See [RFC 8410](https://datatracker.ietf.org/doc/html/rfc8410) for DER encoding of Ed25519 public keys. + + - See [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480) for DER encoding of ECDSA public keys; the DER encoding must not specify a hash function. For curve `secp256k1`, the OID 1.3.132.0.10 is used. The points must be specified in uncompressed form (i.e. `0x04` followed by the big-endian 32-byte encodings of `x` and `y`). + +- The signatures are encoded as the concatenation of the 32-byte big endian encodings of the two values *r* and *s*. + +#### Web Authentication {#webauthn} + +The allowed signature schemes for web authentication are + +- [**ECDSA**](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) on curve P-256 (also known as `secp256r1`), using SHA-256 as hash function. + +- [**RSA PKCS\#1v1.5 (RSASSA-PKCS1-v1\_5)**](https://datatracker.ietf.org/doc/html/rfc8017#section-8.2), using SHA-256 as hash function. + +The signature is calculated by using the payload as the challenge in the web authentication assertion. + +The signature is checked by verifying that the `challenge` field contains the [base64url encoding](https://datatracker.ietf.org/doc/html/rfc4648#section-5) of the payload, and that `signature` verifies on `authenticatorData · SHA-256(utf8(clientDataJSON))`, as specified in the [WebAuthn w3c recommendation](https://www.w3.org/TR/webauthn/#op-get-assertion). + +- The public key is encoded as a DER-wrapped COSE key. + + It uses the `SubjectPublicKeyInfo` type used for other types of public keys (see, e.g., [RFC 8410, Section 4](https://datatracker.ietf.org/doc/html/rfc8410#section-4)), with OID 1.3.6.1.4.1.56387.1.1 (iso.org.dod.internet.private.enterprise.dfinity.mechanisms.der-wrapped-cose). The `BIT STRING` field `subjectPublicKey` contains the COSE encoding. See [WebAuthn w3c recommendation](https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples) or [RFC 8152](https://datatracker.ietf.org/doc/html/rfc8152#section-13.1) for details on the COSE encoding. + +:::tip + +A DER wrapping of a COSE key is shown below. It can be parsed via the command `sed "s/#.*//" | xxd -r -p | openssl asn1parse -inform der`. + + 30 5E # SEQUENCE of length 94 bytes + 30 0C # SEQUENCE of length 12 bytes + 06 0A 2B 06 01 04 01 83 B8 43 01 01 # OID 1.3.6.1.4.1.56387.1.1 + 03 4E 00 # BIT STRING encoding of length 78, + A501 0203 2620 0121 5820 7FFD 8363 2072 # length is at byte boundary + FD1B FEAF 3FBA A431 46E0 EF95 C3F5 5E39 # contents is a valid COSE key + 94A4 1BBF 2B51 74D7 71DA 2258 2032 497E # with ECDSA on curve P-256 + ED0A 7F6F 0009 2876 5B83 1816 2CFD 80A9 + 4E52 5A6A 368C 2363 063D 04E6 ED + +You can also view the wrapping in [an online ASN.1 JavaScript decoder](https://lapo.it/asn1js/#MF4wDAYKKwYBBAGDuEMBAQNOAKUBAgMmIAEhWCB__YNjIHL9G_6vP7qkMUbg75XD9V45lKQbvytRdNdx2iJYIDJJfu0Kf28ACSh2W4MYFiz9gKlOUlpqNowjYwY9BObt). + +::: + +- The signature is a CBOR (see [CBOR](#cbor)) value consisting of a data item with major type 6 ("Semantic tag") and tag value `55799`, followed by a map with three mandatory fields: + + - `authenticator_data` (`blob`): WebAuthn authenticator data. + + - `client_data_json` (`text`): WebAuthn client data in JSON representation. + + - `signature` (`blob`): Signature as specified in the [WebAuthn w3c recommendation](https://www.w3.org/TR/webauthn/#signature-attestation-types), which means DER encoding in the case of an ECDSA signature. + +#### Canister signatures {#canister-signatures} + +The IC also supports a scheme where a canister can sign a payload by declaring a special "certified variable". + +This section makes forward references to other concepts in this document, in particular the section [Certification](./certification.md#certification). + +- The public key is a DER-wrapped structure that indicates the *signing canister*, and includes a freely choosable seed. Each choice of seed yields a distinct public key for the canister, and the canister can choose to encode information, such as a user id, in the seed. + + More concretely, it uses the `SubjectPublicKeyInfo` type used for other types of public keys (see, e.g., [RFC 8410, Section 4](https://datatracker.ietf.org/doc/html/rfc8410#section-4)), with OID 1.3.6.1.4.1.56387.1.2 (iso.org.dod.internet.private.enterprise.dfinity.mechanisms.canister-signature). + + The `BIT STRING` field `subjectPublicKey` is the blob `|signing_canister_id| · signing_canister_id · seed`, where `|signing_canister_id|` is the one-byte encoding of the the length of the `signing_canister_id` and `·` denotes blob concatenation. + +- The signature is a CBOR (see [CBOR](#cbor)) value consisting of a data item with major type 6 ("Semantic tag") and tag value `55799`, followed by a map with two mandatory fields: + + - `certificate` (`blob`): A CBOR-encoded certificate as per [Encoding of certificates](./certification.md#certification-encoding). + + - `tree` (`hash-tree`): A hash tree as per [Encoding of certificates](./certification.md#certification-encoding). + +- Given a payload together with public key and signature in the format described above the signature can be verified by checking the following two conditions: + + - The `certificate` must be a valid certificate as described in [Certification](./certification.md#certification), with + ``` + lookup_path(["canister", , "certified_data"], certificate.tree) = Found (reconstruct(tree)) + ``` + + where `signing_canister_id` is the id of the signing canister and `reconstruct` is a function that computes a root-hash for the tree. + + - If the `certificate` includes a subnet delegation, then the `signing_canister_id` must be included in the delegation's canister id range (see [Delegation](./certification.md#certification-delegation)). + + - The `tree` must be a `well_formed` tree with + ``` + lookup_path(["sig", , ], tree) = Found "" + ``` + + where `s` is the SHA-256 hash of the `seed` used in the public key and `m` is the SHA-256 hash of the payload. + +### Supplementary Technologies {#supplementary-technologies} + +#### CBOR {#cbor} + +[Concise Binary Object Representation (CBOR)](https://www.rfc-editor.org/rfc/rfc8949) is a data format with a small code footprint, small message size and an extensible interface. CBOR is used extensively throughout the Internet Computer as the primary format for data exchange between components within the system. + +[cbor.io](https://cbor.io) and [wikipedia.org](https://en.wikipedia.org/wiki/CBOR) contain a lot of helpful background information and relevant tools. [cbor.me](https://cbor.me) in particular, is very helpful for converting between CBOR hex and diagnostic information. + +For example, the following CBOR hex: +``` +82 61 61 a1 61 62 61 63 +``` + +Can be converted into the following CBOR diagnostic format: +``` +["a", {"b": "c"}] +``` + +Particular concepts to note from the spec are: + +- [Specification of the CBOR Encoding](https://www.rfc-editor.org/rfc/rfc8949#name-specification-of-the-cbor-e) + +- [CBOR Major Types](https://www.rfc-editor.org/rfc/rfc8949#name-major-types) + +- [CBOR Self-Describe](https://www.rfc-editor.org/rfc/rfc8949#self-describe) + +#### CDDL {#cddl} + +The [Concise Data Definition Language (CDDL)](https://datatracker.ietf.org/doc/html/rfc8610) is a data description language for CBOR. It is used at various points throughout this document to describe how certain data structures are encoded with CBOR. + +## The system state tree {#state-tree} + +Parts of the IC state are publicly exposed (e.g. via [Request: Read state](./https-interface.md#http-read-state) or [Certified data](./canister-interface.md#system-api-certified-data)) in a verified way (see [Certification](./certification.md#certification) for the machinery for certifying). This section describes the content of this system state abstractly. + +Conceptually, the system state is a tree with labeled children, and values in the leaves. Equivalently, the system state is a mapping from paths (sequences of labels) to values, where the domain is prefix-free. + +Labels are always blobs (but often with a human readable representation). In this document, paths are written suggestively with slashes as separators; the actual encoding is not actually using slashes as delimiters, and labels may contain the 0x2F byte (ASCII `/`) just fine. Values are either natural numbers, text values or blob values. + +This section specifies the publicly relevant paths in the tree. + +### Time {#state-tree-time} + +- `/time` (natural): + + All partial state trees include a timestamp, expressed in nanoseconds since 1970-01-01, indicating the time at which the state is current. + +### Api boundary nodes information {#state-tree-api-bn} + +The state tree contains information about all API boundary nodes (the source of truth for these API boundary node records is stored in the NNS registry canister). + +- `/api_boundary_nodes//domain` (text) + + Domain name associated with a node. All domains are unique across nodes. + 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`. + +- `/api_boundary_nodes//ipv6_address` (text) + + Public IPv6 address of a node in the hexadecimal notation with colons. + Example: `3002:0bd6:0000:0000:0000:ee00:0033:6778`. + +### Subnet information {#state-tree-subnet} + +The state tree contains information about the topology of the Internet Computer. + +- `/subnet//public_key` (blob) + + The public key of the subnet (a DER-encoded BLS key, see [Certification](./certification.md#certification)) + +- `/subnet//type` (text) + + The subnet type of the subnet. Possible values are "application", "system", "verified_application", and "cloud_engine" (without quotes). + +- `/subnet//canister_ranges` (blob) + + The set of canister ids assigned to this subnet, represented as a list of closed intervals of canister ids, ordered lexicographically, and encoded as CBOR (see [CBOR](#cbor)) according to this CDDL (see [CDDL](#cddl)): + ``` + canister_ranges = tagged<[*canister_range]> + canister_range = [principal principal] + principal = bytes .size (0..29) + tagged = #6.55799(t) ; the CBOR tag + ``` + +- `/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 + +Because this uses the lexicographic ordering of principals, and the byte distinguishing the various classes of ids is at the *end*, this range by construction conceptually includes principals of various classes. This specification needs to take care that the fact that principals that are not canisters may appear in these ranges does not cause confusion. + +::: + +- `/subnet//node//public_key` (blob) + + The public key of a node (a DER-encoded Ed25519 signing key, see [RFC 8410](https://tools.ietf.org/html/rfc8410) for reference) with principal `` belonging to the subnet with principal ``. + + +### Canister ranges {#state-tree-canister-ranges} + +The state tree also stores the canister ID ranges of subnets on the Internet Computer in a sharded form. + +- `/canister_ranges//` (blob) + + The set of canister IDs assigned to this subnet is represented as a **list of closed intervals of canister IDs, ordered lexicographically**. + This list is then split into **non-overlapping shards**, with each shard stored under a path of the above form and encoded as CBOR (see [CBOR](#cbor)). + + Specifically: + 1. Each shard contains a non-empty list of ranges. + 2. The first range in the shard starts with the `` in its path. + 3. The next shard (if any) begins with a strictly greater starting canister ID. + 4. All shards together cover the entire set of canister ID ranges for the subnet without overlap. + + **Example:** Suppose a subnet has these canister ID ranges: + ``` + [1, 3], [5, 8], [10, 12], [20, 25] + ``` + They could be split into two shards: + - `/canister_ranges//1` → `[1, 3], [5, 8]` + - `/canister_ranges//10` → `[10, 12], [20, 25]` + + Each shard is represented as a CBOR-encoded list of ranges. + The encoding follows the same CDDL (see [CDDL](#cddl)) as for subnet-level canister ranges: + + ``` + canister_ranges = tagged<[*canister_range]> ; unlike before, this now represents a single shard + canister_range = [principal principal] + principal = bytes .size (0..29) + tagged = #6.55799(t) ; the CBOR tag + ``` + + **Difference from `/subnet//canister_ranges`:** + - `/subnet//canister_ranges` stores the complete set of ranges in one blob. + - `/canister_ranges//` stores the same ranges split into consecutive shards, each identified by its starting `` in the path. This facilitates e.g., binary searching. + +### Request status {#state-tree-request-status} + +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](./https-interface.md#http-call-overview) for more details on how update call requests work. + +- `/request_status//status` (text) + + One of `received`, `processing`, `replied`, `rejected` or `done`, see [Overview of canister calling](./https-interface.md#http-call-overview) for more details on what each status means. + +- `/request_status//reply` (blob) + + If the status is `replied`, then this path contains the reply blob, else it is not present. + +- `/request_status//reject_code` (natural) + + If the status is `rejected`, then this path contains the reject code (see [Reject codes](./https-interface.md#reject-codes)), else it is not present. + +- `/request_status//reject_message` (text) + + If the status is `rejected`, then this path contains a textual diagnostic message, else it is not present. + +- `/request_status//error_code` (text) + + If the status is `rejected`, then this path might be present and contain an implementation-specific error code (see [Error codes](./https-interface.md#error-codes)), else it is not present. + +:::note + +Immediately after submitting a request, the request may not show up yet as the Internet Computer is still working on accepting the request as pending. + +::: + +:::note + +Request statuses will not actually be kept around indefinitely, and eventually the Internet Computer forgets about the request. This will happen no sooner than the request's expiry time, so that replay attacks are prevented. + +::: + +### Certified data {#state-tree-certified-data} + +- `/canister//certified_data` (blob): + + The certified data of the canister with the given id, see [Certified data](./canister-interface.md#system-api-certified-data). + +### Canister information {#state-tree-canister-information} + +Users have the ability to learn about the hash of the canister's module, its current controllers, and metadata in a certified way. + +- `/canister//module_hash` (blob): + + If the canister is empty, this path does not exist. If the canister is not empty, it exists and contains the SHA256 hash of the currently installed canister module. Cf. [IC method](./management-canister.md#ic-canister_status). + +- `/canister//controllers` (blob): + + The current controllers of the canister. The value consists of a CBOR (see [CBOR](#cbor)) data item with major type 6 ("Semantic tag") and tag value `55799`, followed by an array of principals in their binary form (CDDL `#6.55799([* bytes .size (0..29)])`, see [CDDL](#cddl)). + +- `/canister//metadata/` (blob): + + If the canister has a [custom section](https://webassembly.github.io/spec/core/binary/modules.html#custom-section) called `icp:public ` or `icp:private `, this path contains the content of the custom section. Otherwise, this path does not exist. + + It is recommended for the canister to have a custom section called "icp:public candid:service", which contains the UTF-8 encoding of [the Candid interface](https://github.com/dfinity/candid/blob/master/spec/Candid.md#core-grammar) for the canister. + + diff --git a/docs/references/ic-interface-spec/management-canister.md b/docs/references/ic-interface-spec/management-canister.md new file mode 100644 index 0000000..315ec51 --- /dev/null +++ b/docs/references/ic-interface-spec/management-canister.md @@ -0,0 +1,1100 @@ +--- +title: "IC Management Canister" +description: "The virtual management canister interface: canister lifecycle, threshold signing, Bitcoin, and provisional APIs" +sidebar: + label: "Management Canister" + order: 3 +--- + +## The IC management canister {#ic-management-canister} + +The interfaces above provide the fundamental ability for external users and canisters to contact other canisters. But the Internet Computer provides additional functionality, such as canister and user management. This functionality is exposed to external users and canisters via the *IC management canister*. + +:::note + +The *IC management canister* is just a facade; it does not actually exist as a canister (with isolated state, Wasm code, etc.). + +::: + +The IC management canister address is `aaaaa-aa` (i.e. the empty blob). + +It is possible to use the management canister via external requests (a.k.a. ingress messages). The cost of processing that request is charged to the canister that is being managed. Most methods only permit the controllers to call them. Calls to `raw_rand` and `deposit_cycles` are never accepted as ingress messages. + +### Interface overview {#ic-candid} + +The [interface description](/reference/ic.did), in [Candid syntax](../candid-spec.md), describes the available functionality. + +The binary encoding of arguments and results are as per Candid specification. + +### IC method `create_canister` {#ic-create_canister} + +This method can only be called by canisters and subnet admins, i.e., it cannot be called by external users who are not subnet admins via ingress messages. + +Before deploying a canister, the administrator of the canister first has to register it with the IC, to get a canister id (with an empty canister behind it), and then separately install the code. + +The optional `settings` parameter can be used to set the following settings: + +- `controllers` (`vec principal`) + + A list of at most 10 principals. The principals in this list become the *controllers* of the canister. + Note that the caller of the `create_canister` call is not a controller of the canister + unless it is a member of the `controllers` list. + + Default value: A list containing only the caller of the `create_canister` call. + +- `compute_allocation` (`nat`) + + Must be a number between 0 and 100, inclusively. It indicates how much compute power should be guaranteed to this canister, expressed as a percentage of the maximum compute power that a single canister can allocate. If the IC cannot provide the requested allocation, for example because it is oversubscribed, the call will be rejected. + + Default value: 0 + +- `memory_allocation` (`nat`) + + Must be a number between 0 and 264-1, inclusively. + It indicates an amount of memory in bytes that the canister is guaranteed to be allowed to use in total. + If the IC cannot guarantee the requested memory allocation, for example because it is oversubscribed, then the call will be rejected. + + Default value: 0 + +- `freezing_threshold` (`nat`) + + Must be a number between 0 and 264-1, inclusively, and indicates a length of time in seconds. + + A canister is considered frozen whenever the IC estimates that the canister would be depleted of cycles before `freezing_threshold` seconds pass, given the canister's current size and the IC's current cost for storage. + + Calls to a frozen canister will be rejected with `SYS_TRANSIENT` reject code. Additionally, a canister cannot perform calls if that would, due the cost of the call and transferred cycles, would push the balance into frozen territory; these calls fail with `ic0.call_perform` returning a non-zero error code. + + Default value: 2592000 (approximately 30 days). + +- `reserved_cycles_limit` (`nat`) + + Must be a number between 0 and 2128-1, inclusively, and indicates the upper limit on `reserved_cycles` of the canister. + + An operation that allocates resources such as compute and memory will fail if the new value of `reserved_cycles` exceeds this limit. + + Default value: 5_000_000_000_000 (5 trillion cycles). + +- `wasm_memory_limit` (`nat`) + + Must be a number between 0 and 248-1 (i.e., 256TB), inclusively, and indicates the upper limit on the WASM heap memory consumption of the canister in bytes. + + An operation (update method, canister init, canister post_upgrade) that causes the WASM heap memory consumption to exceed this limit will trap. + The WASM heap memory limit is ignored for query methods, response callback handlers, global timers, heartbeats, and canister pre_upgrade. + + If set to 0, then there's no upper limit on the WASM heap memory consumption of the canister subject to the available memory on the IC. + + Default value: 0 (i.e., no explicit limit). + + Note: in a future release of this specification, the default value and whether the limit is enforced for global timers and heartbeats might change. + +- `log_visibility` (`log_visibility`) + + Controls who can access the canister's logs through the `fetch_canister_logs` endpoint of the management canister. Can be one of: + - `controllers`: Only the canister's controllers can fetch logs + - `public`: Anyone can fetch the canister's logs + - `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can fetch logs, the maximum length of the list is 10 + + Default value: `controllers`. + +- `snapshot_visibility` (`snapshot_visibility`) + + Controls who can access the canister's snapshots through the following endpoints of the management canister: + - `read_canister_snapshot_metadata` + - `read_canister_snapshot_data` + - `list_canister_snapshots` + + Can be one of: + - `controllers`: Only the canister's controllers can read its snapshots + - `public`: Anyone can read the canister's snapshots + - `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can read its snapshots, the maximum length of the list is 10 + + Default value: `controllers`. + +- `wasm_memory_threshold` (`nat`) + + Must be a number between 0 and 264-1, inclusively, and indicates the threshold on the remaining wasm memory size of the canister in bytes: + if the remaining wasm memory size of the canister is below the threshold, execution of the ["on low wasm memory" hook](./canister-interface.md#on-low-wasm-memory) is scheduled. + + Default value: 0 (i.e., the "on low wasm memory" hook is never scheduled). + +- `environment_variables` (`environment_variables`) + + A record containing key-value pairs where both key and value are UTF-8 encoded strings. These variables are accessible to the canister during execution and can be used to configure canister behavior without code changes. Each key must be unique. + + The maximum number of environment variables is implementation-defined. The maximum length of keys and values is implementation-defined. + + Default value: `null` (i.e., no environment variables provided). + +The optional `sender_canister_version` parameter can contain the caller's canister version. If provided, its value must be equal to `ic0.canister_version`. + +Until code is installed, the canister is `Empty` and behaves like a canister that has no public methods. + +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). (No cycles are required on subnets that have a non-empty list of subnet admins.) + +### IC method `update_settings` {#ic-update_settings} + +This method can be called by canisters as well as by external users via ingress messages. + +Only *controllers* of the canister can update settings. See [IC method](#ic-create_canister) for a description of settings. + +Not including a setting in the `settings` record means not changing that field. The defaults described above are only relevant during canister creation. + +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 `upload_chunk` {#ic-upload_chunk} + +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. + +### IC method `stored_chunks` {#ic-stored_chunks} + +This method can be called by canisters as well as by external users via ingress messages. + +Canister controllers (and the canister itself) can list the hashes of chunks in the chunk storage of a canister. + +### IC method `install_code` {#ic-install_code} + +This method can be called by canisters as well as by external users via ingress messages. + +This method installs code into a canister. + +Only controllers of the canister can install code. + +- If `mode = variant { install }`, the canister must be empty before. This will instantiate the canister module and invoke its `canister_init` method (if present), as explained in Section "[Canister initialization](./canister-interface.md#system-api-init)", passing the `arg` to the canister. + +- If `mode = variant { reinstall }`, if the canister was not empty, its existing code and state (including stable memory) is removed before proceeding as for `mode = install`. + + Note that this is different from `uninstall_code` followed by `install_code`, as `uninstall_code` generates a synthetic reject response to all callers of the uninstalled canister that the uninstalled canister did not yet reply to and ensures that callbacks to outstanding calls made by the uninstalled canister won't be executed (i.e., upon receiving a response from a downstream call made by the uninstalled canister, the cycles attached to the response are refunded, but no callbacks are executed). + +- If `mode = variant { upgrade }` or `mode = variant { upgrade = opt record { skip_pre_upgrade = .., wasm_memory_persistence = .. } }`, this will perform an upgrade of a non-empty canister as described in [Canister upgrades](./canister-interface.md#system-api-upgrades), passing `arg` to the `canister_post_upgrade` method of the new instance. If `skip_pre_upgrade = opt true`, then the `canister_pre_upgrade` method on the old instance is not executed. If `wasm_memory_persistence = opt keep`, then the WebAssembly memory is preserved. + +This is atomic: If the response to this request is a `reject`, then this call had no effect. + +:::note + +Some canisters may not be able to make sense of callbacks after upgrades; these should be stopped first, to wait for all outstanding callbacks, or be uninstalled first, to prevent outstanding callbacks from being invoked (by marking the corresponding call contexts as deleted). It is expected that the canister admin (or their tooling) does that separately. + +::: + +The `wasm_module` field specifies the canister module to be installed. The system supports multiple encodings of the `wasm_module` field, as described in [Canister module format](./canister-interface.md#canister-module-format): + +- If the `wasm_module` starts with byte sequence `[0x00, 'a', 's', 'm']`, the system parses `wasm_module` as a raw WebAssembly binary. + +- If the `wasm_module` starts with byte sequence `[0x1f, 0x8b, 0x08]`, the system parses `wasm_module` as a gzip-compressed WebAssembly binary. + +The optional `sender_canister_version` parameter can contain the caller's canister version. If provided, its value must be equal to `ic0.canister_version`. + +This method traps if the canister's cycle balance decreases below the canister's freezing limit after executing the method. + +### IC method `install_chunked_code` {#ic-install_chunked_code} + +This method can be called by canisters as well as by external users via ingress messages. + +This method installs code that had previously been uploaded in chunks. + +Only controllers of the target canister can call this method. + +The `mode`, `arg`, and `sender_canister_version` parameters are as for `install_code`. +The `target_canister` specifies the canister where the code should be installed. +The optional `store_canister` specifies the canister in whose chunk storage the chunks are stored (this parameter defaults to `target_canister` if not specified). +For the call to succeed, the caller must be a controller of the `store_canister` or the caller must be the `store_canister`. The `store_canister` must be on the same subnet as the target canister. + +The `chunk_hashes_list` specifies a list of hash values `[h1,...,hk]` with `k <= MAX_CHUNKS_IN_LARGE_WASM`. The system looks up in the chunk store of `store_canister` (or that of the target canister if `store_canister` is not specified) blobs corresponding to `h1,...,hk` and concatenates them to obtain a blob of bytes referred to as `wasm_module` in `install_code`. It then checks that the SHA-256 hash of `wasm_module` is equal to the `wasm_module_hash` parameter and calls `install_code` with parameters `(record {mode; target_canister; wasm_module; arg; sender_canister_version})`. + +### IC method `uninstall_code` {#ic-uninstall_code} + +This method can be called by canisters as well as by external users via ingress messages. + +This method removes a canister's code and state, making the canister *empty* again. + +Only controllers of the canister or subnet admins can uninstall code. + +Uninstalling a canister's code will reject all calls that the canister has not yet responded to, and drop the canister's code and state. Outstanding responses to the canister will not be processed, even if they arrive after code has been installed again. Cycles attached to such responses will still be refunded though. + +The canister is now [empty](./index.md#canister-lifecycle). In particular, any incoming or queued calls will be rejected. + +A canister after *uninstalling* retains its *cycle* balances, *controllers*, history, status, and allocations. + +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 `canister_status` {#ic-canister_status} + +This method can be called by canisters as well as by external users via ingress messages. +This method can also be called by external users via non-replicated (query) calls, but it cannot be called from composite query calls. + +Indicates various information about the canister. It contains: + +- The status of the canister. It could be one of `running`, `stopping` or `stopped`. + +- A bool `ready_for_migration` indicating whether a stopped canister is ready to be migrated to another subnet (i.e., whether it has empty queues and flushed streams). This flag can only ever be `true` if the `status` variant (see above) is `stopped`. This property is guaranteed by the protocol, but deliberately not on the type level in order to facilitate backwards compatible service evolution. + +- The canister version. + +- The "settings" of the canister containing: + + - The controllers of the canister. The order of returned controllers may vary depending on the implementation. + + - The compute allocation of the canister. + + - The memory allocation of the canister in bytes. + + - The freezing threshold of the canister in seconds. + + - The reserved cycles limit of the canister, i.e., the maximum number of cycles that can be in the canister's reserved balance after increasing the canister's memory allocation and/or actual memory usage. + + - The visibility of the canister's logs. + + - The visibility of the canister's snapshots. + + - The WASM heap memory limit of the canister in bytes (the value of `0` means that there is no explicit limit). + + - The "low wasm memory" threshold, which is used to determine when the [canister_on_low_wasm_memory](./canister-interface.md#on-low-wasm-memory) function is executed. + + - The environment variables of the canister, which is a record containing key-value pairs used to configure the canister's behavior. + +- A SHA256 hash of the module installed on the canister. This is `null` if the canister is empty. + +- The actual memory usage of the canister, representing the total memory consumed by the canister. + +- A record containing detailed breakdown of memory usage into individual components (see [Memory Metrics](#ic-canister_status-memory_metrics) for more details). + +- The cycle balance of the canister. + +- The reserved cycles balance of the canister, i.e., the number of cycles reserved when increasing the canister's memory allocation and/or actual memory usage. + +- The idle cycle consumption of the canister, i.e., the number of cycles burned by the canister per day due to its compute and memory allocation and actual memory usage. + +- Statistics regarding the query call execution of the canister, i.e., a record containing the following fields: + + * `num_calls_total`: the total number of query and composite query methods evaluated on the canister, + + * `num_instructions_total`: the total number of WebAssembly instructions executed during the evaluation of query and composite query methods, + + * `request_payload_bytes_total`: the total number of query and composite query method payload bytes, and + + * `response_payload_bytes_total`: the total number of query and composite query response payload (reply data or reject message) bytes. + +Only the controllers of the canister or the canister itself or subnet admins can request its status. + +#### Memory Metrics {#ic-canister_status-memory_metrics} + + * `wasm_memory_size`: Represents the Wasm memory usage of the canister, i.e. the heap memory used by the canister's WebAssembly code. + + * `stable_memory_size`: Represents the stable memory usage of the canister. + + * `global_memory_size`: Represents the memory usage of the global variables that the canister is using. + + * `wasm_binary_size`: Represents the memory occupied by the Wasm binary that is currently installed on the canister. This is the size of the binary uploaded via `install_code` or `install_chunked_code`, e.g., the compressed size if the uploaded binary is gzipped. + + * `custom_sections_size`: Represents the memory used by custom sections defined by the canister, which may include additional metadata or configuration data. + + * `canister_history_size`: Represents the memory used for storing the canister's history. + + * `wasm_chunk_store_size`: Represents the memory used by the Wasm chunk store of the canister. + + * `snapshots_size`: Represents the memory consumed by all snapshots that belong to this canister. + +All sizes are expressed in bytes. + +### 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. + +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, loading a snapshot, controllers change, canister renaming). 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). +- Canister creation is described by the full set of controllers along with a [hash of the environment variables](./https-interface.md#hash-of-map), if environment variables were specified. The order of controllers stored in the canister history may vary depending on the implementation. +- Code deployment is described by its 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 ID of the canister from which the snapshot was loaded (if that canister ID is different than the canister ID onto which the snapshot is loaded), the snapshot ID, the canister version and timestamp at which the snapshot was taken (the canister version and timestamp refer to the canister from which the snapshot was loaded), and the source of the snapshot (canister state or metadata upload). +- Controllers change is described by the full new set of controllers after the change. The order of controllers stored in the canister history may vary depending on the implementation. +- Canister renaming is described by the canister ID and the total number of canister changes before renaming as well as the canister ID, the canister version, and the total number of canister changes of the new canister ID. Because only a dedicated NNS canister can perform canister renaming, the actual principal who requested canister renaming is recorded in a separate field `requested_by`. The total number of canister changes reported by the IC method `canister_info` is overriden to the total number of canister changes of the new canister ID. Canister changes referring to the canister ID before renaming are preserved. + +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. + +The following parameters should be supplied for the call: + +- `canister_id`: the canister ID of the canister to retrieve information about. + +- `num_requested_changes`: optional, specifies the number of requested canister changes. If not provided, the default value of `0` will be used. + +The returned response contains the following fields: + +- `total_num_changes`: the total number of canister changes that have been ever recorded in the history. This value does not change if the system drops the oldest canister changes from the list of changes. + +- `recent_changes`: the list containing the most recent canister changes. If `num_requested_changes` is provided, then this list contains that number of changes or, if more changes are requested than available in the history, then this list contains all changes available in the history. If `num_requested_changes` is not specified, then this list is empty. + +- `module_hash`: the SHA-256 hash of the currently installed canister module (or `null` if the canister is empty). + +- `controllers`: the current set of canister controllers. The order of returned controllers may vary depending on the implementation. + +### IC method `canister_metadata` {#ic-canister_metadata} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +Provides access to canister's metadata contained in custom sections whose names have the form `icp:public ` or `icp:private ` +(see [Canister module format](./canister-interface.md#canister-module-format) for requirements on custom sections). + +Every canister can retrieve public metadata (in custom sections whose names have the form `icp:public `) of every other canister (including itself). +Only controllers of a canister can access its private metadata (in custom sections whose names have the form `icp:private `). + +The following parameters should be supplied for the call: + +- `canister_id` (`principal`): the canister ID of the canister to retrieve metadata from. + +- `name` (`text`): identifies canister's metadata contained in a custom section whose name has the form `icp:public ` or `icp:private ` + (note that a canister cannot have custom sections with both `icp:public ` or `icp:private ` as names for the same ``, see [Canister module format](./canister-interface.md#canister-module-format)). + +The returned response contains the following fields: + +- `value` (`blob`): the content of canister's metadata identified by the given `name`. + +### IC method `stop_canister` {#ic-stop_canister} + +This method can be called by canisters as well as by external users via ingress messages. + +The controllers of a canister or subnet admins may stop a canister (e.g., to prepare for a canister upgrade). + +When this method successfully returns, then the canister status is `stopped` at that point. +However, note that the canister might be restarted at any time due to a concurrent call. + +The execution of this method proceeds as follows: + +- The immediate effect is that the status of the canister is changed to `stopping` (unless the canister is already stopped). +- The IC now rejects all calls to a stopping canister, indicating that the canister is stopping. Responses to a stopping canister are processed as usual. +- When all outstanding responses have been processed (so that there are no open call contexts), the canister status is changed to `stopped`. +- If the canister status is changed to `stopped` within an implementation-specific timeout, then this method successfully returns. +- Otherwise, this method returns an error (the canister status is still `stopping` and might eventually become `stopped` if all outstanding responses have been processed and the canister has not been restarted by a separate call). + +### IC method `start_canister` {#ic-start_canister} + +This method can be called by canisters as well as by external users via ingress messages. + +A canister may be started by its controllers or subnet admins. + +If the canister status was `stopped` or `stopping` then the canister status is simply set to `running`. In the latter case all `stop_canister` calls which are processing fail (and are rejected). + +If the canister was already `running` then the status stays unchanged. + +### IC method `delete_canister` {#ic-delete_canister} + +This method can be called by canisters as well as by external users via ingress messages. + +This method deletes a canister from the IC. + +Only controllers of the canister or subnet admins can delete it and the canister must already be stopped. Deleting a canister cannot be undone, any state stored on the canister is permanently deleted and its cycles are discarded. Once a canister is deleted, its ID cannot be reused. + +### IC method `deposit_cycles` {#ic-deposit_cycles} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method deposits the cycles included in this call into the specified canister. + +### IC method `raw_rand` {#ic-raw_rand} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method takes no input and returns 32 pseudo-random bytes to the caller. The return value is unknown to any part of the IC at time of the submission of this call. A new return value is generated for each call to this method. + +### IC method `ecdsa_public_key` {#ic-ecdsa_public_key} + +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 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 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} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method returns a new [ECDSA](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. This public key can be obtained by calling `ecdsa_public_key` with the caller's `canister_id`, and the same `derivation_path` and `key_id` used here. + +:::note + +If the signing request returns a reject response whose reject code is `SYS_UNKNOWN` or `CANISTER_ERROR`, the signature may exist in the system even though it's not returned to the requesting canister. Thus, canisters should not rely on the signature not existing in these cases. + +::: + +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 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} + +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} + +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`. + +:::note + +If the signing request returns a reject response whose reject code is `SYS_UNKNOWN` or `CANISTER_ERROR`, the signature may exist in the system even though it's not returned to the requesting canister. Thus, canisters should not rely on the signature not existing in these cases. + +::: + +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). Note that the returned signature is **non-deterministic** and a different signature may be computed every time it is requested. + +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 rejected. + +This call accepts an optional auxiliary parameter `aux`. The auxiliary parameter type `schnorr_aux` is an enumeration. The only currently supported variant is `bip341` which allows passing a Merkle tree root hash, which is required to implement Taproot signatures as defined in [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki). The `bip341` variant is only allowed for `bip340secp256k1` signatures, and if provided the `merkle_root_hash` must be generated in accordance with BIP341's specification for `taproot_output_script`. Specifically it should be either an empty bytestring (for the `script == None` case) or else 32 bytes generated using the procedure documented as `taproot_tree_helper`. If no auxiliary parameter is provided, then `bip340secp256k1` signatures are generated in accordance with BIP340. + +On the Internet Computer, the tuple of the requested master key, the calling canister, and derivation path determines which private key is used to generate the signature, and which public key is returned by `schnorr_public_key`. + +When using BIP341 signatures, the actual signature that is created will be relative to the Schnorr signature derived as described in BIP341's `taproot_sign_script`. The key returned by `schnorr_public_key` is the value identified in BIP341 as `internal_pubkey`. + +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 `vetkd_public_key` {#ic-vetkd_public_key} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method returns the vetKD public (verification) key derived from the vetKD master public key with ID `key_id` for the canister with the given `canister_id` and the given `context`. + +If the `canister_id` is unspecified, it will default to the canister id of the caller. The `context` is a byte string of variable length. The `key_id` is a struct specifying both a curve and a name. The availability of a particular `key_id` depends on the implementation. + +The public key returned for an empty `context` is called _canister public key_. Given this canister public key, the public key for a particular `context` can also be derived offline. + +For curve `bls12_381_g2`, the returned `public_key` is a G2 element in compressed form in [BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381) encoding. + +This call requires that a vetKD master key with ID `key_id` was generated by the IC and the key derivation functionality for that key was enabled, and that the `canister_id` meets the requirement of a canister id. Otherwise, the call is is rejected. + +### IC method `vetkd_derive_key` {#ic-vetkd_derive_key} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method returns a vetKD key (aka vetKey) encrypted under `transport_public_key` and derived from the vetKD master key with ID `key_id` based on the caller's `input` for a given `context`. + +Both the `input` and the `context` are byte strings of variable length. While both are inputs to the underlying key derivation algorithm (implicitly together with the calling canister's ID), `input` is intended as the primary differentiator when deriving different keys, while `context` is intended as domain separator. +The `key_id` is a struct specifying both a curve and a name. The availability of a particular `key_id` depends on the implementation. + +Both the encrypted and the decrypted form of the vetKD key can be verified by using the respective vetKD public (verification) key, which can be obtained by calling the IC method `vetkd_public_key`. + +For curve `bls12_381_g2`, the following holds: + +- The `transport_public_key` is a G1 element in compressed form in [BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381) encoding. Transport public keys are created by calculating *tpk = g1tsk*, where the transport secret key *tsk* is chosen uniformly at random from Zp. + +- The returned `encrypted_key` is the blob `E1 · E2 · E3`, where E1 and E3 are G1 elements, and E2 is a G2 element, all in compressed form in [BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381) encoding. + + The encrypted key can be verified by ensuring *e(E1, g2) == e(g1, E2)*, and *e(E3, g2) == e(tpk, E2) \* e(H(dpk · `input`), dpk)*, where *dpk* is the derived (vetKD) public key associated with the respective `context`, `key_id`, and the canister ID of the caller. + +- The decrypted vetKD key *k* is obtained by calculating E3 \* E1-tsk, where tsk ∈ Zp is the transport secret key that was used to generate the `transport_public_key`. + + The key can be verified by ensuring *e(k, g2) == e(H(dpk · `input`), dpk)*, where *dpk* is the derived (vetKD) public key associated with the respective `context`, `key_id`, and the canister ID of the caller. Such verification protects against untrusted canisters returning invalid keys. + +where + +- g1, g2 are generators of G1, G2, which are groups of prime order *p*, + +- \* denotes the group operation in G1, G2, and GT, + +- e: `G1 x G2 → GT` is the pairing (see [BLS Signatures Draft RFC, Appendix A](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-bls12-381)), + +- H hashes into G1 according to the [BLS12-381 message augmentation scheme ciphersuite in the BLS Signatures Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature#name-message-augmentation-2) (see also [Hashing to Elliptic Curves Draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve#name-suites-for-bls12-381)), + +- `·` and · denote concatenation + +This call requires that a vetKD master key with ID `key_id` was generated by the IC and the key derivation 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). + +### IC method `http_request` {#ic-http_request} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +This method makes an HTTP request to a given URL and returns the HTTP response, possibly after a transformation. + +The method can be called in either replicated or non-replicated mode. In the replicated mode, the same HTTP request is performed by multiple IC replicas, providing strong guarantees on the integrity of the response. In the non-replicated mode, the request is made by a single replica, with weak integrity guarantees. + +:::note + +The non-replicated mode is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. + +::: + +Both because of replication and to handle network issues, the canister should aim to issue *idempotent* requests, meaning that it must not change the state at the remote server, or that the remote server has the means to identify duplicated requests. Otherwise, the risk of failure increases. + +In the replicated mode, the responses for all identical requests must match, too. However, a web service could return slightly different responses for identical idempotent requests. For example, it may include some unique identification or a timestamp that would vary across responses. + +For this reason, the calling canister can supply a transformation function, which the IC uses to let the canister sanitize the responses from such unique values. The transformation function is executed separately on the corresponding response received for a request (both in replicated and non-replicated modes). Only the transformed response will be available to the calling canister. + +Currently, the `GET`, `HEAD`, and `POST` methods are supported for HTTP requests. Additionally, the `PUT` and `DELETE` methods are supported in non-replicated mode only. `PUT` and `DELETE` are restricted to non-replicated mode to avoid confusing race conditions that may occur with replicated execution. + +It is important to note the following for the usage of the `POST` method: + +- The calling canister must make sure that the remote server is able to recognize requests as duplicates of each other and apply only one of them, even if they are sent from multiple sources. This may require, for example, to set a certain request header to uniquely identify the request. This is especially important in the replicated mode. + +- There is no guarantee that all sent requests are as specified by the canister. + +Furthermore, for all methods, the following holds: + +- There are no confidentiality guarantees on the request or response content. + +- In the replicated mode, if the canister receives a response, then at least one request that was sent matched the canister's request, and the response was to that request. In the non-replicated mode, there are no such guarantees. The canister should not assume the integrity of the response and must check it by some other means. + +For security reasons, only HTTPS connections are allowed (URLs must start with `https://`). The IC uses industry-standard root CA lists to validate certificates of remote web servers. + +The **size** of an HTTP request from the canister or an HTTP response from the remote HTTP server is the total number of bytes representing the names and values of HTTP headers and the HTTP body. The maximal size for the request from the canister is `2MB` (`2,000,000B`). Each request can specify a maximal size for the response from the remote HTTP server. The upper limit on the maximal size for the response is `2MB` (`2,000,000B`) and this value also applies if no maximal size value is specified. An error will be returned when the request or response is larger than the maximal size. + +The following parameters should be supplied for the call: + +- `url` - the requested URL. The URL must be valid according to [RFC-3986](https://www.ietf.org/rfc/rfc3986.txt), it might contain non-ASCII characters according to [RFC-3987](https://www.ietf.org/rfc/rfc3987.txt), and its length must not exceed `8192`. The URL may specify a custom port number. + +- `max_response_bytes` - optional, specifies the maximal size of the response in bytes. If provided, the value must not exceed `2MB` (`2,000,000B`). The call will be charged based on this parameter. If not provided, the maximum of `2MB` will be used. + +- `method` - currently, `GET`, `HEAD`, and `POST` are supported. Additionally, `PUT` and `DELETE` are supported in non-replicated mode only. + +- `headers` - list of HTTP request headers and their corresponding values + +- `body` - optional, the content of the request's body + +- `transform` - an optional record that includes a function that transforms raw responses to sanitized responses, and a byte-encoded context that is provided to the function upon invocation, along with the response to be sanitized. If provided, the calling canister itself must export this function + +- `is_replicated` - optional, selecting between replicated and non-replicated modes. + +:::note + +The `is_replicated` field is considered EXPERIMENTAL. + +::: + +Cycles to pay for the call must be explicitly transferred with the call, i.e., they are not automatically deducted from the caller's balance implicitly (e.g., as for inter-canister calls). + +The returned response (and the response provided to the `transform` function, if specified) contains the following fields: + +- `status` - the response status (e.g., 200, 404) + +- `headers` - list of HTTP response headers and their corresponding values + +- `body` - the response's body + +The `transform` function may, for example, transform the body in any way, add or remove headers, modify headers, etc. The maximal number of bytes representing the response produced by the `transform` function is equal to `max_response_bytes`, if provided, otherwise the default value of `2MB` (`2,000,000B`) is used as the limit. Note that the number of bytes representing the response produced by the `transform` function includes the serialization overhead of the encoding produced by the canister. + +When the transform function is invoked by the system due to a canister HTTP request, the caller's identity is the principal of the management canister. This information can be used by developers to implement an access control mechanism for this function. + +The following additional limits apply to HTTP requests and HTTP responses from the remote server: + +- the number of headers must not exceed `64`, + +- the number of bytes representing a header name or value must not exceed `8KiB`, and + +- the total number of bytes representing the header names and values must not exceed `48KiB`. + +If the request headers provided by the canister do not contain a `user-agent` header (case-insensitive), +then the IC sends a `user-agent` header (case-insensitive) with the value `ic/1.0` +in addition to the headers provided by the canister. Such an additional header does not contribute +to the above limits on HTTP request headers. + +:::note + +The Internet Computer mainnet supports requests to both IPv6 and IPv4 destinations. The system prioritizes a direct connection to IPv6 addresses (i.e., the domain has a `AAAA` DNS record). If a direct connection cannot be established (e.g., the domain only has an IPv4 address via an A record), the request is automatically retried through a proxy. + +::: + +:::warning + +If you do not specify the `max_response_bytes` parameter, the maximum of a `2MB` response will be charged for, which is expensive in terms of cycles. Always set the parameter to a reasonable upper bound of the expected (network and transformed) response size to not incur unnecessary cycles costs for your request. + +::: + +### IC method `node_metrics_history` {#ic-node_metrics_history} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +:::note + +The node metrics management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. + +::: + +Given a subnet ID as input, this method returns a time series of node metrics (field `node_metrics`). The timestamps are represented as nanoseconds since 1970-01-01 (field `timestamp_nanos`) at which the metrics were sampled. The returned timestamps are all timestamps after (and including) the provided timestamp (field `start_at_timestamp_nanos`) for which node metrics are available. The maximum number of returned timestamps is 60 and no two returned timestamps belong to the same UTC day. + +Note that a sample will only include metrics for nodes whose metrics changed compared to the previous sample. This means that if a node disappears in one sample and later reappears its metrics will restart from 0 and consumers of this API need to adjust for these resets when aggregating over multiple samples. + +A single metric entry is a record with the following fields: + +- `node_id` (`principal`): the principal characterizing a node; + +- `num_blocks_proposed_total` (`nat64`): the number of blocks proposed by this node; + +- `num_block_failures_total` (`nat64`): the number of failed block proposals by this node. + +### IC method `subnet_info` {#ic-subnet_info} + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +Given a subnet ID as input, this method returns a record `subnet_info` containing metadata about that subnet. + +The fields returned are: +- `replica_version` (`text`) of the targeted subnet +- `registry_version` (`nat64`) of the targeted subnet + +### 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. + +A `take_canister_snapshot` call creates a new snapshot. However, the call might fail if the maximum number of snapshots per canister is reached. This error can be avoided by providing an existing snapshot ID via the optional `replace_snapshot` parameter. That existing snapshot will be deleted once a new snapshot has been successfully created. + +It's important to note that a new snapshot might increase the memory footprint of the canister. Thus, the canister's balance must have a sufficient amount of cycles so that the canister does not become frozen. This issue can be mitigated by uninstalling code of the canister via the optional `uninstall_code` parameter after a new snapshot has been successfully created. The exact semantics of uninstalling code is described in the section on the IC method [`uninstall_code`](#ic-uninstall_code). + +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. + +::: + +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 `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. + +The snapshot can only be loaded onto a canister that belongs to the same subnet as the canister from which the snapshot is loaded. + +The caller must be a controller of + +- the canister onto which the snapshot is loaded; and + +- the canister from which the snapshot is loaded. + +:::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 `read_canister_snapshot_metadata` {#ic-read_canister_snapshot_metadata} + +This method can be called by canisters as well as by external users via ingress messages. + +Who is allowed to read the metadata of a snapshot of that canister is determined by the field `snapshot_visibility` in `canister_settings` and can be one of the following variants: +- `controllers`: Only the canister's controllers can read its snapshots metadata +- `public`: Anyone can read the canister's snapshots metadata +- `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can read snapshots metadata, the maximum length of the list is 10 + +This method returns all metadata of a snapshot identified by `snapshot_id` of the canister identified by `canister_id`. It fails if no snapshot with the specified `snapshot_id` can be found for that canister. + +The returned metadata of a snapshot contain: + +- the "source" of the snapshot, i.e., whether the snapshot was created by taking the canister state using the method [`take_canister_snapshot`](#ic-take_canister_snapshot) or by (snapshot) metadata upload using the method [`upload_canister_snapshot_metadata`](#ic_upload_canister_snapshot_metadata); + +- the timestamp at which the snapshot was created, i.e., the method [`take_canister_snapshot`](#ic-take_canister_snapshot) or [`upload_canister_snapshot_metadata`](#ic_upload_canister_snapshot_metadata) executed; + +- the size of the canister WASM (in bytes); + +- values of WASM globals (not to be confused with global variables in a high-level programming language) that are either exported or mutable in the canister WASM; + +- sizes of WASM (a.k.a. heap) and stable memory (in bytes); + +- hashes of chunks in the WASM chunk store; + +- the [canister version](./canister-interface.md#system-api-canister-version) when the snapshot was created, i.e., the method [`take_canister_snapshot`](#ic-take_canister_snapshot) or [`upload_canister_snapshot_metadata`](#ic_upload_canister_snapshot_metadata) executed; + +- the [certified data](./canister-interface.md#system-api-certified-data); + +- (optional) the state of the [global timer](./canister-interface.md#global-timer), i.e., whether it is inactive or active with a deadline (in nanoseconds since 1970-01-01); + +- (optional) the state of the [on low wasm memory](./canister-interface.md#on-low-wasm-memory) hook, i.e., whether the condition for the hook to be scheduled is not satisfied, the hook is ready to be executed (i.e., the hook has been scheduled), or the hook has already been executed. + +The state of the global timer and on low wasm memory hook are `null` for existing snapshots created before release [release-2025-04-03_03-15-base (68fc31a141b25f842f078c600168d8211339f422](https://dashboard.internetcomputer.org/release/68fc31a141b25f842f078c600168d8211339f422) rolled out between April 7, 2025, and April 14, 2025, in the ICP mainnet. + +### IC method `read_canister_snapshot_data` {#ic-read_canister_snapshot_data} + +This method can be called by canisters as well as by external users via ingress messages. + +Who is allowed to read a snapshot of that canister is determined by the field `snapshot_visibility` in `canister_settings` and can be one of the following variants: +- `controllers`: Only the canister's controllers can read its snapshots +- `public`: Anyone can read the canister's snapshots +- `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can read snapshots, the maximum length of the list is 10 + +This method returns a requested kind of (binary) data from a snapshot identified by `snapshot_id` of the canister identified by `canister_id`. It fails if no snapshot with the specified `snapshot_id` can be found for that canister. + +The following kinds of (binary) data from a snapshot can be requested: + +- chunk of the canister WASM starting at a given `offset` and with a given `size` of the chunk (`offset + size` must not exceed the canister WASM size as in the snapshot metadata); + +- chunk of the WASM (a.k.a. heap) memory starting at a given `offset` and with a given `size` of the chunk (`offset + size` must not exceed the WASM memory size as in the snapshot metadata); + +- chunk of the stable memory starting at a given `offset` and with a given `size` of the chunk (`offset + size` must not exceed the stable memory size as in the snapshot metadata); + +- (full) chunk in the WASM chunk store identified by its `hash` (`hash` must be present in the snapshot metadata). + +### IC method `upload_canister_snapshot_metadata` {#ic-upload_canister_snapshot_metadata} + +This method can be called by canisters as well as by external users via ingress messages. + +Only controllers of a canister can create a snapshot of that canister by uploading the snapshot's metadata. + +An `upload_canister_snapshot_metadata` call creates a new snapshot. However, the call might fail if the maximum number of snapshots per canister is reached. This error can be avoided by providing an existing snapshot ID via the optional `replace_snapshot` parameter. That existing snapshot will be deleted once a new snapshot has been successfully created (in particular, before data is uploaded to that new snapshot using subsequent `upload_canister_snapshot_data` calls). + +It's important to note that a new snapshot will increase the memory footprint of the canister. Thus, the canister's balance must have a sufficient amount of cycles so that the canister does not become frozen. + +Uploaded metadata of a snapshot contain: + +- the size of the canister WASM (in bytes); + +- values of WASM globals (not to be confused with global variables in a high-level programming language) that are either exported or mutable in the canister WASM; + +- sizes of WASM (a.k.a. heap) and stable memory (in bytes); + +- the [certified data](./canister-interface.md#system-api-certified-data); + +- (optional) the state of the [global timer](./canister-interface.md#global-timer), i.e., whether it is inactive or active with a deadline (in nanoseconds since 1970-01-01); + +- (optional) the state of the [on low wasm memory](./canister-interface.md#on-low-wasm-memory) hook, i.e., whether the condition for the hook to be scheduled is not satisfied, the hook is ready to be executed (i.e., the hook has been scheduled), or the hook has already been executed. + +If the state of the global timer and/or the on low wasm memory hook are not provided in the uploaded metadata, +then their state is not updated when loading the snapshot using the method `load_canister_snapshot`. + +### IC method `upload_canister_snapshot_data` {#ic-upload_canister_snapshot_data} + +This method can be called by canisters as well as by external users via ingress messages. + +Only controllers of a canister can upload data to a snapshot of that canister. + +This method uploads a provided (binary) chunk of a provided kind of (binary) data to a snapshot identified by `snapshot_id` of the canister identified by `canister_id`. It fails if no snapshot with the specified `snapshot_id` can be found for that canister or if the snapshot with the specified `snapshot_id` has been created using the method `take_canister_snapshot` (i.e., not by uploading snapshot metadata). + +The following kinds of (binary) data can be uploaded to a snapshot: + +- chunk of the canister WASM starting at a given `offset` (`offset + |chunk|` must not exceed the canister WASM size as in the snapshot metadata); + +- chunk of the WASM (a.k.a. heap) memory starting at a given `offset` (`offset + |chunk|` must not exceed the WASM memory size as in the snapshot metadata); + +- chunk of the stable memory starting at a given `offset` (`offset + |chunk|` must not exceed the stable memory size as in the snapshot metadata); + +- (full) chunk in the WASM chunk store (the length `|chunk|` of the provided chunk must be at most 1MiB and the maximum number of chunks in the chunk store of the snapshot is `CHUNK_STORE_SIZE` chunks). + +It's important to note that uploading a chunk to the WASM chunk store of the snapshot will increase the memory footprint of the canister. Thus, the canister's balance must have a sufficient amount of cycles so that the canister does not become frozen. On the other hand, uploading a chunk to the canister WASM, WASM (a.k.a.) heap memory, and stable memory does increase the memory footprint of the canister since their sizes have been fixed when uploading the snapshot's metadata. + +### 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`. + +Who is allowed to list the snapshots of that canister is determined by the field `snapshot_visibility` in `canister_settings` and can be one of the following variants: +- `controllers`: Only the canister's controllers can list its snapshots +- `public`: Anyone can list the canister's snapshots +- `allowed_viewers` (`vec principal`): Only principals in the provided list and the canister's controllers can list the snapshots, the maximum length of the list is 10 + +### 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` or `upload_canister_snapshot_metadata` 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 that the snapshot belongs to. + +### IC method `fetch_canister_logs` {#ic-fetch_canister_logs} + +This method can only be called by external users via non-replicated (query) calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls. + +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, TQ, CQ, CRy, CRt, CC, and F modes, as defined in [Overview of imports](./canister-interface.md#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` and can be one of the following variants: + +- `controllers`: only the canister's controllers can fetch logs (default); +- `public`: everyone can fetch logs; +- `allowed_viewers` (`vec principal`): only principals in the provided list and the canister's controllers can fetch logs, the maximum length of the list is 10. + +A single log is a record with the following fields: + +- `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; + +:::warning + +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. + +::: + +### IC method `list_canisters` {#ic-list_canisters} + +This method can only be called by external users with subnet admin privileges via non-replicated (query) calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls. + +This method returns the list of all canisters on the subnet as consecutive canister ID ranges. Deleted canisters are not included in the result. + +A canister ID range is a record with the following fields: + +- `start` (`principal`): the first canister ID in the range (inclusive); +- `end` (`principal`): the last canister ID in the range (inclusive). + +:::warning + +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 IC Bitcoin API {#ic-bitcoin-api} + +The Bitcoin API exposed by the management canister is DEPRECATED. +Developers should interact with the Bitcoin canisters (`ghsi2-tqaaa-aaaan-aaaca-cai` for Bitcoin mainnet and `g4xu7-jiaaa-aaaan-aaaaq-cai` for Bitcoin testnet) directly. +Information about Bitcoin and the IC Bitcoin integration can be found in the [Bitcoin developer guides](https://developer.bitcoin.org/devguide/) and the [Bitcoin integration documentation](../../guides/chain-fusion/bitcoin.md). + +### IC method `bitcoin_get_utxos` {#ic-bitcoin_get_utxos} + +:::note + +This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. + +::: + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +Given a `get_utxos_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns all unspent transaction outputs (UTXOs) associated with the provided address in the specified Bitcoin network based on the current view of the Bitcoin blockchain available to the Bitcoin component. The UTXOs are returned sorted by block height in descending order. + +The following address formats are supported: + +- Pay to public key hash (P2PKH) + +- Pay to script hash (P2SH) + +- Pay to witness public key hash (P2WPKH) + +- Pay to witness script hash (P2WSH) + +- Pay to taproot (P2TR) + +If the address is malformed, the call is rejected. + +The optional `filter` parameter can be used to restrict the set of returned UTXOs, either providing a minimum number of confirmations or a page reference when pagination is used for addresses with many UTXOs. In the first case, only UTXOs with at least the provided number of confirmations are returned, i.e., transactions with fewer than this number of confirmations are not considered. In other words, if the number of confirmations is `c`, an output is returned if it occurred in a transaction with at least `c` confirmations and there is no transaction that spends the same output with at least `c` confirmations. + +There is an upper bound of 144 on the minimum number of confirmations. If a larger minimum number of confirmations is specified, the call is rejected. Note that this is not a severe restriction as the minimum number of confirmations is typically set to a value around 6 in practice. + +It is important to note that the validity of transactions is not verified in the Bitcoin component. The Bitcoin component relies on the proof of work that goes into the blocks and the verification of the blocks in the Bitcoin network. For a newly discovered block, a regular Bitcoin (full) node therefore provides a higher level of security than the Bitcoin component, which implies that it is advisable to set the number of confirmations to a reasonably large value, such as 6, to gain confidence in the correctness of the returned UTXOs. + +There is an upper bound of 10,000 UTXOs that can be returned in a single request. For addresses that contain sufficiently many UTXOs, a partial set of the address's UTXOs are returned along with a page reference. + +In the second case, a page reference (a series of bytes) must be provided, which instructs the Bitcoin component to collect UTXOs starting from the corresponding "page". + +A `get_utxos_request` without the optional `filter` results in a request that considers the full blockchain, which is equivalent to setting `min_confirmations` to 0. + +The recommended workflow is to issue a request with the desired number of confirmations. If the `next_page` field in the response is not empty, there are more UTXOs than in the returned vector. In that case, the `page` field should be set to the `next_page` bytes in the subsequent request to obtain the next batch of UTXOs. + +### IC method `bitcoin_get_balance` {#ic-bitcoin_get_balance} + +:::note + +This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. + +::: + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +Given a `get_balance_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns the current balance of this address in `Satoshi` (10^8 Satoshi = 1 Bitcoin) in the specified Bitcoin network. The same address formats as for [`bitcoin_get_utxos`](#ic-bitcoin_get_utxos) are supported. + +If the address is malformed, the call is rejected. + +The optional `min_confirmations` parameter can be used to limit the set of considered UTXOs for the calculation of the balance to those with at least the provided number of confirmations in the same manner as for the [`bitcoin_get_utxos`](#ic-bitcoin_get_utxos) call. + +Given an address and the optional `min_confirmations` parameter, `bitcoin_get_balance` iterates over all UTXOs, i.e., the same balance is returned as when calling [`bitcoin_get_utxos`](#ic-bitcoin_get_utxos) for the same address and the same number of confirmations and, if necessary, using pagination to get all UTXOs for the same tip hash. + +### IC method `bitcoin_send_transaction` {#ic-bitcoin_send_transaction} + +:::note + +This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. + +::: + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +Given a `send_transaction_request`, which must specify a `blob` of a Bitcoin transaction and a Bitcoin network (`mainnet` or `testnet`), several checks are performed: + +- The transaction is well formed. + +- The transaction only consumes unspent outputs with respect to the current (longest) blockchain, i.e., there is no block on the (longest) chain that consumes any of these outputs. + +- There is a positive transaction fee. + +If at least one of these checks fails, the call is rejected. + +If the transaction passes these tests, the transaction is forwarded to the specified Bitcoin network. Note that the function does not provide any guarantees that the transaction will make it into the mempool or that the transaction will ever appear in a block. + +### IC method `bitcoin_get_current_fee_percentiles` {#ic-bitcoin_get_current_fee_percentiles} + +:::note + +This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. + +::: + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +The transaction fees in the Bitcoin network change dynamically based on the number of pending transactions. It must be possible for a canister to determine an adequate fee when creating a Bitcoin transaction. + +This function returns fee percentiles, measured in millisatoshi/vbyte (1000 millisatoshi = 1 satoshi), over the last 10,000 transactions in the specified network, i.e., over the transactions in the last approximately 4-10 blocks. + +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 `bitcoin_get_block_headers` {#ic-bitcoin_get_block_headers} + +:::note + +This method is DEPRECATED. Canister developers are advised to call the method of the same name on the Bitcoin (mainnet or testnet) canister. + +::: + +This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages. + +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. + +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. + +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 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. + + diff --git a/docs/reference/icrc-standards.md b/docs/references/icrc-standards.md similarity index 100% rename from docs/reference/icrc-standards.md rename to docs/references/icrc-standards.md diff --git a/docs/reference/index.md b/docs/references/index.md similarity index 92% rename from docs/reference/index.md rename to docs/references/index.md index 9043f34..cd0b3d7 100644 --- a/docs/reference/index.md +++ b/docs/references/index.md @@ -32,7 +32,7 @@ Technical reference material for ICP development. These pages cover exact specif ## Specifications -- **[IC Interface Specification](ic-interface-spec.md)**: System API, HTTP interface, and certified data specification. +- **[IC Interface Specification](ic-interface-spec/index.md)**: System API, HTTPS interface, certified data, management canister, and formal specification of the Internet Computer. - **[HTTP Gateway Specification](http-gateway-spec.md)**: How boundary nodes serve canister HTTP responses with certification verification. - **[Candid Specification](candid-spec.md)**: The Candid interface description language: type system, encoding, and subtyping rules. - **[Internet Identity Specification](internet-identity-spec.md)**: Delegation chains, passkey management, and canister signatures. diff --git a/docs/reference/internet-identity-spec.md b/docs/references/internet-identity-spec.md similarity index 97% rename from docs/reference/internet-identity-spec.md rename to docs/references/internet-identity-spec.md index 590985e..0396f34 100644 --- a/docs/reference/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -87,7 +87,7 @@ A user account is identified by a unique *Identity Anchor*, a natural number cho A client application frontend is identified by its origin (e.g., `https://abcde-efg.ic0.app`, `https://nice-name.com`). Frontend applications can be served by canisters or by websites that are not hosted on the Internet Computer. -A user has a separate *user identity* for each client application frontend (i.e., per origin). This identity is a [*self-authenticating id*](./ic-interface-spec.md#id-classes) of the [DER encoded canister signature public key](./ic-interface-spec.md/#canister-signatures) which has the form +A user has a separate *user identity* for each client application frontend (i.e., per origin). This identity is a [*self-authenticating id*](./ic-interface-spec/index.md#id-classes) of the [DER encoded canister signature public key](./ic-interface-spec/index.md#canister-signatures) which has the form ``` user_id = SHA-224(DER encoded public key) · 0x02` (29 bytes) ``` @@ -108,9 +108,9 @@ where `H` is SHA-256, `·` is concatenation, `|…|` is a single byte representi A `frontend_origin` of the form `https://.icp0.io` will be rewritten to `https://.ic0.app` before being used in the seed. This ensures transparent pseudonym transfer between apps hosted on `ic0.app` and `icp0.io` domains. ::: -When a client application frontend wants to authenticate as a user, it uses a *session key* (e.g., Ed25519 or ECDSA), and by way of the authentication flow (details below) obtains a [*delegation chain*](./ic-interface-spec.md#authentication) that allows the session key to sign for the user's main identity. +When a client application frontend wants to authenticate as a user, it uses a *session key* (e.g., Ed25519 or ECDSA), and by way of the authentication flow (details below) obtains a [*delegation chain*](./ic-interface-spec/https-interface.md#authentication) that allows the session key to sign for the user's main identity. -The delegation chain consists of one delegation, called the *client delegation*. It delegates from the user identity (for the given client application frontend) to the session key. This delegation is created by the Internet Identity Service Canister, and signed using a [canister signature](./ic-interface-spec.md/#canister-signatures). This delegation is unscoped (valid for all canisters) and has a maximum lifetime of 30 days, with a default of 30 minutes. +The delegation chain consists of one delegation, called the *client delegation*. It delegates from the user identity (for the given client application frontend) to the session key. This delegation is created by the Internet Identity Service Canister, and signed using a [canister signature](./ic-interface-spec/index.md#canister-signatures). This delegation is unscoped (valid for all canisters) and has a maximum lifetime of 30 days, with a default of 30 minutes. The Internet Identity service frontend also manages an *identity frontend delegation*, delegating from the security device's public key to a session key managed by this frontend, so that it can interact with the backend without having to invoke the security device for each signature. @@ -203,7 +203,7 @@ sequenceDiagram } ``` - where the `userPublicKey` is the user's Identity on the given frontend and `delegations` corresponds to the CBOR-encoded delegation chain as used for [*authentication on the IC*](./ic-interface-spec.md#authentication) and `authnMethod` is the method used by the user to authenticate (`passkey` for webauthn, `pin` for temporary key/PIN identity, and `recovery` for recovery phrase or recovery device). + where the `userPublicKey` is the user's Identity on the given frontend and `delegations` corresponds to the CBOR-encoded delegation chain as used for [*authentication on the IC*](./ic-interface-spec/https-interface.md#authentication) and `authnMethod` is the method used by the user to authenticate (`passkey` for webauthn, `pin` for temporary key/PIN identity, and `recovery` for recovery phrase or recovery device). 9. It could also receive a failure message of the following type ```ts @@ -739,7 +739,7 @@ type WebAuthn = record { }; // Authentication method using generic signatures -// See ./ic-interface-spec.md/#signatures for +// See ./ic-interface-spec/index.md#signatures for // supported signature schemes. type PublicKeyAuthn = record { pubkey : PublicKey; @@ -1455,7 +1455,7 @@ Since this cannot be done during `canister_init` (no calls from canister init), ### Why we do not use `canister_inspect_message` -The system allows canisters to inspect ingress messages before they are actually ingressed, and decide if they want to pay for them (see [the interface spec](./ic-interface-spec.md/#system-api-inspect-message)). Because the Internet Identity canisters run on a system subnet, cycles are not actually charged, but we still want to avoid wasting resources. +The system allows canisters to inspect ingress messages before they are actually ingressed, and decide if they want to pay for them (see [the interface spec](./ic-interface-spec/canister-interface.md#system-api-inspect-message)). Because the Internet Identity canisters run on a system subnet, cycles are not actually charged, but we still want to avoid wasting resources. It seems that this implies that we should use `canister_inspect_message` to reject messages that would, for example, not pass authentication. @@ -1471,10 +1471,10 @@ Therefore, the Internet Identity Canister intentionally does not implement `cani diff --git a/docs/reference/protocol-canisters.md b/docs/references/protocol-canisters.md similarity index 100% rename from docs/reference/protocol-canisters.md rename to docs/references/protocol-canisters.md diff --git a/docs/reference/subnet-types.md b/docs/references/subnet-types.md similarity index 100% rename from docs/reference/subnet-types.md rename to docs/references/subnet-types.md diff --git a/docs/reference/system-canisters.md b/docs/references/system-canisters.md similarity index 100% rename from docs/reference/system-canisters.md rename to docs/references/system-canisters.md diff --git a/sidebar.mjs b/sidebar.mjs index dfb9266..4ef125b 100644 --- a/sidebar.mjs +++ b/sidebar.mjs @@ -78,7 +78,7 @@ export const sidebar = [ collapsed: true, autogenerate: { directory: "concepts" }, }, - { slug: "reference/developer-tools", label: "Developer Tools" }, + { slug: "references/developer-tools", label: "Developer Tools" }, { label: "Languages", items: [ @@ -116,8 +116,28 @@ export const sidebar = [ ], }, { - label: "Reference", + label: "References", collapsed: true, - autogenerate: { directory: "reference" }, + items: [ + { slug: "references/management-canister" }, + { slug: "references/system-canisters" }, + { slug: "references/protocol-canisters" }, + { slug: "references/application-canisters" }, + { slug: "references/icrc-standards" }, + { slug: "references/digital-asset-standards" }, + { slug: "references/chain-key-canister-ids" }, + { slug: "references/cycles-costs" }, + { slug: "references/subnet-types" }, + { slug: "references/execution-errors" }, + { slug: "references/http-gateway-spec" }, + { slug: "references/candid-spec" }, + { slug: "references/internet-identity-spec" }, + { + label: "IC Interface Spec", + collapsed: true, + autogenerate: { directory: "references/ic-interface-spec" }, + }, + { slug: "references/glossary" }, + ], }, ];