Resource Limiting#
Deterministic Mode and Non-Deterministic Mode have separate RAM budgets: what one consumes is never charged to the other. The deterministic budget starts at 4294967295 octets (4 GiB). Every RunNondet Message gets its own non-deterministic budget, starting at what its caller had remaining at the moment of the call, so a nondet block never gets more RAM than its caller had left. A sub-VM does not share its caller’s budget. It starts from a copy of what the caller had remaining and spends from that copy, so what a sub-VM spends never reduces what its caller may still spend, and a caller that spawns many children in sequence is not drained by them. Only the charges for data the caller keeps after a child returns move to the caller; see RAM Release.
RAM Consumption#
Every resource allocation subtracts from the RAM budget of the current VM Execution Modes.
When an allocation would cause the remaining budget to become negative,
the sub-VM exits with VMError carrying the
out_of memory message, or one of its
out_of memory wasm_memory /
out_of memory wasm_table variants for the
corresponding WASM allocations. memory.grow and table.grow are the
exception: at runtime they are recoverable rather than fatal (see the two
bullets below).
The following operations consume RAM:
WASM memory growth: each page (65536 octets) costs its size in bytes. A runtime
memory.growthat would exceed the budget is not fatal: following the WASM specification it leaves memory unchanged and evaluates to \(-1\), so the guest can react. Only the memory’s initial, instantiation-time reservation being unmet makes the sub-VM exit with out_of memory wasm_memory.WASM table growth: each table entry costs table_entry octets. As with memory, a runtime
table.growbeyond the budget evaluates to \(-1\); only the instantiation-time reservation being unmet exits with out_of memory wasm_table.File mapping: file_mapping octets base cost plus the length of the filename in bytes
File descriptor allocation: fd_allocation octets per descriptor
Runner loading: the first load of a runner in a sub-VM consumes its load charge, including ZIP metadata. A runner already in that sub-VM’s loaded set costs nothing, and the charge is released when the sub-VM finishes, like any other charge. Loading covers spawning the entry-point runner,
Depends/Withactions, theMapFileandRegisterRunnergl_calls, and receiving a custom-runner grant at sub-VM creation (see Runners and Custom-Runner Grants). A sub-VM holds at most max_runners runners: a load past that fails the same way an exhausted budget does, and charges nothingStorage writes: writing to a 32-octet aligned region of a Storage Slot costs new_storage_page octets the first time that region is written. Regions the sub-VM inherited already written from its caller, and repeated writes to a region, cost nothing
Emissions: each emitted message or event costs execution_emission_base_size octets, plus the encoded length of each payload it retains — calldata, code, allocation subtree, topics and event data — and message_fee_rotation_element_size octets per retained message-fee rotation. Calldata is charged by its encoded length, so positional and keyword arguments carry no charge of their own
Nondeterministic outputs: each output costs nondet_output_base_size octets plus its encoded length on every role
Sub-VM creation: each new sub-VM costs vm_spawn_cost octets, plus storage_page_inherited octets for every 32-octet region already written in the storage it inherits. Both are charged to the new sub-VM at creation (see Startup) and released when it finishes
The runner load cost (runner_load_cost) is a fixed per-load overhead
Nondeterministic Output Caps#
Before entering a non-deterministic sub-VM, the caller must have enough RAM to retain the canonical memory-limit and non-deterministic-output fee-limit results and return either as a file descriptor. It also checks whether both results can be charged against the non-deterministic-output fee; when either cannot, the sub-VM is not entered
An output that cannot fit its non-deterministic-output fee charge is first replaced with a out_of receipt nondet_output result. If that result, or an output within the fee limit, cannot fit its RAM charge, it is replaced with a out_of memory result. The leader publishes the replacement and an honest replay charges the same encoded result
A validator rejects a leader proposal that differs from its post-cap result as a fatal leader_fault nondet_output malformed result without entering the validator sub-VM
RAM Release#
File content memory is released when the corresponding file descriptor is closed via fd_close.
When a sub-VM finishes execution, its copy of the budget is discarded,
so every charge it still held is released at once.
This applies to runner charges as well: memory consumed by loading or
registering a runner is released when the registering sub-VM finishes,
like any other charge.
Charges for retained storage, emissions, and nondeterministic outputs are permanent. When a Sandbox Message child returns and its caller takes over the child’s retained data, those charges are transferred to the caller rather than released.
Other Limits#
In addition to the RAM budget, the following hard limits apply:
Maximum sub-VM nesting depth: vm_recursion
Maximum
RunNondetcalls per execution: nondet_blocksMaximum locked storage slots: locked_slots
Minimum remaining RAM to issue a
WebRequest: web_request_min_space octetsMinimum remaining RAM to issue a
WebRender: web_render_min_space octets
Exceeding any of these limits causes the sub-VM to exit with VMError.