diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index a99ca39..781820a 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 @@ -2640,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] = { @@ -2930,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) @@ -2937,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) @@ -3192,6 +3203,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 @@ -3373,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] = { @@ -3527,6 +3540,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; @@ -4055,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] = { @@ -4147,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] @@ -4732,6 +4751,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 +4830,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 86e2fcf..01f80f5 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 3f0dd3f..957f9b4 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 c54ba66..5a2b093 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.