Skip to content

Deterministic execution

Necter’s whole security model rests on one property: the same call on the same state produces the same receipt hash on every honest machine. Committees can then vote on a hash, validators can audit by re-executing, and anyone can check a result later. NDSR enforces this at several layers.

A Hive Bytecode module may import only the host functions of its ABI (hive-wasm-v1) (hive.call, hive.emit, hive.abort, storage.get/set/del, console.log, crypto.hash, and AssemblyScript’s env.abort). Any other import, including any WASI function, makes the module invalid. So inside a module there is:

  • no clock: pass timestamps in the input;
  • no randomness: derive values from the input with crypto.hash, or use a scheduled project’s slot seed;
  • no filesystem, network or environment: everything a call needs must be in its input or the module’s own state.
Rule Why
Enabled: MVP, multi-value, bulk memory, reference types, sign-extension, saturating float→int, SIMD, tail calls Deterministic features only
Disabled: threads/shared memory, relaxed SIMD, memory64, multi-memory, GC, exceptions, stack switching, … Their results can differ between hardware or engines
Every NaN result is canonicalized (f64 0/0 → 0x7ff8000000000000) Different CPUs produce different NaN bit patterns
NDSR’s engine and bytecode validator versions are pinned Fuel accounting is defined by that implementation; upgrading is a network upgrade
A deterministic logical stack limit (65,536 units) is injected into every module The native and portable engines’ stacks overflow at different depths; the injected limit is always reached first, at the same gas everywhere

On x86_64 and aarch64 (also riscv64, s390x), NDSR’s native engine compiles modules to machine code. 32-bit ARM phones have no native backend, so NDSR uses its portable engine (an interpreter). Both are fed the same fuel-instrumented, NaN-canonicalized code, so output, events, gas_used, success and receipt hash are identical. This is tested side by side for every fixture, and it is why a phone can sit on the same committee as a server.

Every hashed or signed structure uses canonical JSON: keys sorted by code point, no whitespace, UTF-8 without escaping non-ASCII characters, integers only (no floats), and strings for numbers that can exceed 2^53. Receipts exclude timestamps and node ids. Hashes are Keccak-256 (Ethereum’s, not SHA3-256).

Python equivalent:

json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()

Within a node, executions are serializable: each module’s state transitions are totally ordered. Across nodes, the Hub never has two rounds touching the same module’s state in flight at once, so every validator applies them in the same order. Committee workers are stateless, so ordering never matters for them.

See Determinism rules for the language-by-language list of what to avoid (for example Math.random(), Date.now(), map iteration order, floats in events).