diff --git a/spec/_attachments/ic.did b/spec/_attachments/ic.did index 3b634a68..717fd680 100644 --- a/spec/_attachments/ic.did +++ b/spec/_attachments/ic.did @@ -13,6 +13,7 @@ type canister_settings = record { freezing_threshold : opt nat; reserved_cycles_limit : opt nat; log_visibility : opt log_visibility; + wasm_memory_limit : opt nat; }; type definite_canister_settings = record { @@ -22,6 +23,7 @@ type definite_canister_settings = record { freezing_threshold : nat; reserved_cycles_limit : nat; log_visibility : log_visibility; + wasm_memory_limit : nat; }; type change_origin = variant { diff --git a/spec/_attachments/interface-spec-changelog.md b/spec/_attachments/interface-spec-changelog.md index 0cefcc9f..cafb92bd 100644 --- a/spec/_attachments/interface-spec-changelog.md +++ b/spec/_attachments/interface-spec-changelog.md @@ -5,6 +5,7 @@ * 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. ### 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`. diff --git a/spec/index.md b/spec/index.md index 1a9afcc9..b24f8a43 100644 --- a/spec/index.md +++ b/spec/index.md @@ -2009,6 +2009,19 @@ The optional `settings` parameter can be used to set the following settings: 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. + + 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. + 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. @@ -2112,6 +2125,8 @@ Indicates various information about the canister. It contains: - The canister log visibility of the canister. + - The WASM heap memory limit of the canister in bytes (the value of `0` means that there is no explicit limit). + - 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. @@ -3101,6 +3116,7 @@ Finally, we can describe the state of the IC as a record having the following fi balances: CanisterId ↦ Nat; reserved_balances: CanisterId ↦ Nat; reserved_balance_limits: CanisterId ↦ Nat; + wasm_memory_limit: CanisterId ↦ Nat; certified_data: CanisterId ↦ Blob; canister_history: CanisterId ↦ CanisterHistory; canister_log_visibility: CanisterId ↦ CanisterLogVisibility; @@ -3172,6 +3188,7 @@ The initial state of the IC is balances = (); reserved_balances = (); reserved_balance_limits = (); + wasm_memory_limit = (); certified_data = (); canister_history = (); canister_log_visibility = (); @@ -3686,26 +3703,31 @@ Available = S.call_contexts[M.call_contexts].available_cycles ( M.entry_point = PublicMethod Name Caller Arg F = Mod.update_methods[Name](Arg, Caller, 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, Caller, Env) New_canister_version = S.canister_version[M.receiver] + Wasm_memory_limit = 0 ) or ( M.entry_point = Callback Callback Response RefundedCycles F = Mod.callbacks(Callback, Response, 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 ) R = F(S.canisters[M.receiver].wasm_state) @@ -3747,6 +3769,7 @@ if (S.memory_allocation[M.receiver] = 0) or (memory_usage_wasm_state(res.new_state) + memory_usage_raw_module(S.canisters[M.receiver].raw_module) + memory_usage_canister_history(S.canister_history[M.receiver]) ≤ S.memory_allocation[M.receiver]) + (Wasm_memory_limit = 0) or |res.new_state.store.mem| <= Wasm_memory_limit (res.response = NoResponse) or S.call_contexts[M.call_context].needs_to_respond then S with @@ -3978,6 +4001,10 @@ 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 Cycles_reserved = cycles_to_reserve(S, Canister_id, New_compute_allocation, New_memory_allocation, EmptyCanister.wasm_state) New_balance = M.transferred_cycles - Cycles_reserved @@ -4032,6 +4059,7 @@ S with 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 certified_data[Canister_id] = "" query_stats[Canister_id] = [] canister_history[Canister_id] = New_canister_history @@ -4104,6 +4132,10 @@ 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] Cycles_reserved = cycles_to_reserve(S, A.canister_id, New_compute_allocation, New_memory_allocation, S.canisters[A.canister_id].wasm_state) New_balance = S.balances[A.canister_id] - Cycles_reserved @@ -4159,6 +4191,7 @@ S with 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 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 @@ -4208,6 +4241,7 @@ S with memory_allocation = S.memory_allocation[A.canister_id]; freezing_threshold = S.freezing_threshold[A.canister_id]; reserved_cycles_limit = S.reserved_balance_limit[A.canister_id]; + wasm_memory_limit = S.wasm_memory_limit[A.canister_id]; } module_hash = if S.canisters[A.canister_id] = EmptyCanister @@ -4455,6 +4489,8 @@ if S.memory_allocation[A.canister_id] > 0: memory_usage_raw_module(A.wasm_module) + memory_usage_canister_history(New_canister_history) ≤ S.memory_allocation[A.canister_id] +(S.wasm_memory_limit[A.canister_id] = 0) or |New_state.store.mem| <= S.wasm_memory_limit[A.canister_id] + S.canister_history[A.canister_id] = { total_num_changes = N; recent_changes = H; @@ -4633,6 +4669,8 @@ if S.memory_allocation[A.canister_id] > 0: memory_usage_raw_module(A.wasm_module) + memory_usage_canister_history(New_canister_history) ≤ S.memory_allocation[A.canister_id] +(S.wasm_memory_limit[A.canister_id] = 0) or |New_state.store.mem| <= S.wasm_memory_limit[A.canister_id] + S.canister_history[A.canister_id] = { total_num_changes = N; recent_changes = H; @@ -5044,6 +5082,7 @@ S with 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) certified_data[A.canister_id] = (deleted) canister_history[A.canister_id] = (deleted) canister_log_visibility[A.canister_id] = (deleted) @@ -5201,6 +5240,10 @@ 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 Cycles_reserved = cycles_to_reserve(S, Canister_id, New_compute_allocation, New_memory_allocation, EmptyCanister.wasm_state) if A.amount is not null: @@ -5257,6 +5300,7 @@ S with 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 certified_data[Canister_id] = "" canister_history[Canister_id] = New_canister_history canister_log_visibility[Canister_id] = New_canister_log_visibility