From 7dfb9cda3934845df73769fa6205acef59557c66 Mon Sep 17 00:00:00 2001 From: Stefan Schneider Date: Wed, 29 Jul 2026 07:29:09 +0000 Subject: [PATCH 1/3] feat: Certification versions V27 and V28 Update to the specifications related to the new `last_install_timestamp` and `canister_creation_timestamp` leaves in the state tree. --- .../ic-interface-spec/abstract-behavior.md | 13 +++++++++++++ docs/references/ic-interface-spec/changelog.md | 7 +++++++ .../references/ic-interface-spec/https-interface.md | 4 ++++ docs/references/ic-interface-spec/index.md | 10 +++++++++- 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index a99ca398..b2a8ca43 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -515,6 +515,8 @@ S = { environment_variables: CanisterId ↦ (Text ↦ Text) on_low_wasm_memory_hook_status: CanisterId ↦ OnLowWasmMemoryHookStatus; certified_data: CanisterId ↦ Blob; + canister_creation_timestamp: CanisterId ↦ Timestamp; + last_install_timestamp: CanisterId ↦ Timestamp; canister_history: CanisterId ↦ CanisterHistory; canister_log_visibility: CanisterId ↦ CanisterLogVisibility; canister_snapshot_visibility: CanisterId ↦ CanisterSnapshotVisibility; @@ -625,6 +627,8 @@ The initial state of the IC is environment_variables = (); on_low_wasm_memory_hook_status = (); certified_data = (); + canister_creation_timestamp = (); + last_install_timestamp = (); canister_history = (); canister_log_visibility = (); canister_snapshot_visibility = (); @@ -1755,6 +1759,7 @@ S' = S with canisters[Canister_id] = EmptyCanister snapshots[A.canister_id] = null time[Canister_id] = CurrentTime + canister_creation_timestamp[Canister_id] = CurrentTime global_timer[Canister_id] = 0 controllers[Canister_id] = New_controllers chunk_store[Canister_id] = () @@ -2395,6 +2400,7 @@ S' = S with else: global_timer[A.canister_id] = 0 canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + last_install_timestamp[A.canister_id] = S.time[A.canister_id] balances[A.canister_id] = New_balance reserved_balances[A.canister_id] = New_reserved_balance canister_history[A.canister_id] = New_canister_history @@ -2561,6 +2567,7 @@ S' = S with else: global_timer[A.canister_id] = 0 canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 + last_install_timestamp[A.canister_id] = S.time[A.canister_id] balances[A.canister_id] = New_balance; reserved_balances[A.canister_id] = New_reserved_balance; canister_history[A.canister_id] = New_canister_history @@ -3192,6 +3199,7 @@ S' = S with canisters[Canister_id] = EmptyCanister snapshots[Canister_id] = null time[Canister_id] = CurrentTime + canister_creation_timestamp[Canister_id] = CurrentTime global_timer[Canister_id] = 0 controllers[Canister_id] = New_controllers compute_allocation[Canister_id] = New_compute_allocation @@ -3527,6 +3535,7 @@ S' = S with 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 + last_install_timestamp[A.canister_id] = S.time[A.canister_id] messages = Older_messages · Younger_messages · ResponseMessage { origin = M.origin; @@ -4732,6 +4741,8 @@ 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, "canister_creation_timestamp"]) = cid == ECID +may_read_path_for_canister(S, _, ["canister", cid, "last_install_timestamp"]) = 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 ∨ @@ -4809,6 +4820,8 @@ state_tree(S) = { { canister_id : { "module_hash" : SHA256(C.raw_module) | if C ≠ EmptyCanister } ∪ { "controllers" : CBOR(S.controllers[canister_id]) } ∪ + { "canister_creation_timestamp" : S.canister_creation_timestamp[canister_id] } ∪ + { "last_install_timestamp" : S.last_install_timestamp[canister_id] | if C ≠ EmptyCanister } ∪ { "metadata": { name: blob | (name, blob) ∈ S.canisters[canister_id].public_custom_sections ∪ S.canisters[canister_id].private_custom_sections } } | (canister_id, C) ∈ S.canisters }; } diff --git a/docs/references/ic-interface-spec/changelog.md b/docs/references/ic-interface-spec/changelog.md index 86e2fcfb..01f80f57 100644 --- a/docs/references/ic-interface-spec/changelog.md +++ b/docs/references/ic-interface-spec/changelog.md @@ -8,6 +8,13 @@ sidebar: ## Changelog {#changelog} +### 0.65.0 (2026-07-24) {$0_65_0} +* Two new paths in the certified state tree, `/canister//canister_creation_timestamp` + (the time at which the canister was created) and `/canister//last_install_timestamp` + (the time at which the canister's code was most recently deployed or a snapshot was loaded onto it), + both expressed in nanoseconds since 1970-01-01. Both can be requested via `read_state` if + `` matches the effective canister id of the request. + ### 0.64.0 (2026-07-06) {$0_64_0} * New optional `permissions` field in request delegations restricting the kinds of requests the delegation applies for: the value `"queries"` restricts the delegation to query calls diff --git a/docs/references/ic-interface-spec/https-interface.md b/docs/references/ic-interface-spec/https-interface.md index 3f0dd3f0..957f9b40 100644 --- a/docs/references/ic-interface-spec/https-interface.md +++ b/docs/references/ic-interface-spec/https-interface.md @@ -314,6 +314,10 @@ All requested paths must have the following form: - `/canister//controllers`. Can be requested if `` matches ``. The order of controllers in the value at this path may vary depending on the implementation. +- `/canister//canister_creation_timestamp`. Can be requested if `` matches ``. + +- `/canister//last_install_timestamp`. Can be requested if `` matches ``. + - `/canister//metadata/`. Can be requested if `` matches ``, `` is encoded in UTF-8, and - canister with canister id `` does not exist or diff --git a/docs/references/ic-interface-spec/index.md b/docs/references/ic-interface-spec/index.md index c54ba669..5a2b0935 100644 --- a/docs/references/ic-interface-spec/index.md +++ b/docs/references/ic-interface-spec/index.md @@ -615,7 +615,7 @@ Request statuses will not actually be kept around indefinitely, and eventually t ### 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. +Users have the ability to learn about the hash of the canister's module, its current controllers, metadata, creation time, and last install time in a certified way. - `/canister//module_hash` (blob): @@ -625,6 +625,14 @@ Users have the ability to learn about the hash of the canister's module, its cur 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//canister_creation_timestamp` (natural): + + The time at which the canister was created, expressed in nanoseconds since 1970-01-01. If the canister was created before this information was recorded, this path does not exist. + +- `/canister//last_install_timestamp` (natural): + + The time at which the canister's code was most recently deployed ([code install, reinstall, or upgrade](./management-canister.md#ic-install_code)) or a [snapshot was loaded](./management-canister.md#ic-load_canister_snapshot) onto it, expressed in nanoseconds since 1970-01-01. If the canister is empty, or its code was deployed before this information was recorded, this path does not exist. + - `/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. From 5680aa848b9e705aae1f9336e99437e54a3b01e1 Mon Sep 17 00:00:00 2001 From: Stefan Schneider Date: Wed, 29 Jul 2026 09:22:02 +0000 Subject: [PATCH 2/3] reviewer comments --- docs/references/ic-interface-spec/abstract-behavior.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index b2a8ca43..62349902 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -2647,6 +2647,7 @@ State after S with canisters[A.canister_id] = EmptyCanister + last_install_timestamp[A.canister_id] = (deleted) certified_data[A.canister_id] = "" chunk_store = () canister_history[A.canister_id] = { @@ -2937,6 +2938,8 @@ S with canister_version[A.canister_id] = (deleted) canister_subnet[A.canister_id] = (deleted) time[A.canister_id] = (deleted) + canister_creation_timestamp[A.canister_id] = (deleted) + last_install_timestamp[A.canister_id] = (deleted) global_timer[A.canister_id] = (deleted) balances[A.canister_id] = (deleted) reserved_balances[A.canister_id] = (deleted) @@ -2944,6 +2947,7 @@ S with minimum_incoming_canister_call_cycles[A.canister_id] = (deleted) wasm_memory_limit[A.canister_id] = (deleted) wasm_memory_threshold[A.canister_id] = (deleted) + environment_variables[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) From aaa5ce34866a116ffdde80b7f8b33180434dc358 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 29 Jul 2026 09:43:31 +0000 Subject: [PATCH 3/3] fixes --- docs/references/ic-interface-spec/abstract-behavior.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index 62349902..781820af 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -3385,6 +3385,7 @@ S' = S with reserved_balances[A.canister_id] = New_reserved_balance canisters[A.canister_id] = EmptyCanister + last_install_timestamp[A.canister_id] = (deleted) certified_data[A.canister_id] = "" chunk_store = () canister_history[A.canister_id] = { @@ -4068,6 +4069,7 @@ State after S with canisters[CanisterId] = EmptyCanister + last_install_timestamp[Canister_id] = (deleted) snapshots[CanisterId] = null certified_data[CanisterId] = "" canister_history[CanisterId] = { @@ -4160,6 +4162,10 @@ State after S with canisters[New_canister_id] = S.canisters[Canister_id] canisters[Canister_id] = (deleted) + canister_creation_timestamp[New_canister_id] = S.canister_creation_timestamp[Canister_id] + canister_creation_timestamp[Canister_id] = (deleted) + last_install_timestamp[New_canister_id] = S.last_install_timestamp[Canister_id] + last_install_timestamp[Canister_id] = (deleted) snapshots[New_canister_id] = {} snapshots[Canister_id] = (deleted) controllers[New_canister_id] = S.controllers[Canister_id]