Skip to content

Application Execution

An application is a WASM module that defines a state machine: data types and the methods that mutate them. An execution is one method call. It reads the context’s current state, runs deterministic guest code, and produces a set of state changes plus a fresh root hash. Those state changes are the data-plane operations that fold into the projection.

Crucially, execution is sandboxed and self-contained: the guest cannot touch the network, the clock, or the disk directly. It can only call back into the host through a fixed set of functions — the ABI. Everything the guest does is therefore observable, bounded, and reproducible.

The guest and the host share nothing but a block of linear memory and a set of numbered registers. Arguments cross the boundary as (pointer, length) descriptors into guest memory; the host reads or writes those bytes, and hands larger results back through registers the guest then reads. The host bounds-checks every access.

WASM guest (sandbox) linear memory (ephemeral, per execution) export: method() -> () no args, no return cannot reach network / clock / disk directly ABI boundary host call (ptr,len) result via register Host runtime registers · buffers bounds checks · limits crypto · time · random replicated state private state ordered index node storage

The ABI is a fixed catalog of host functions. The guest never does anything outside this list — which is exactly why execution stays deterministic and bounded. The families:

FamilyExamplesPurpose
Context / inputcontext_id, executor_id, input, read_registerRead who is calling, in which context, with what arguments.
Statestorage_read, storage_write, storage_removeRead and mutate replicated context state. Writes are buffered (below).
Outputvalue_return, log, emitReturn a result, log a line, emit an application event.
Commitcommit / root-state functionsFinalize buffered writes into a new state root and an artifact.
Cryptoed25519_verifyVerify signatures inside guest logic (deterministic).
Host-providedtime_now, random_bytesThe only sources of non-determinism — injected by the host, never read by the guest itself.
Cross-contextxcallQueue a fire-and-forget call into another context on the same node.
Blobsblob_*Stream large binary artifacts outside the state tree.

Not everything a guest writes should replicate. The ABI exposes three distinct layers, and the guest chooses per write:

LayerDescription
ReplicatedThe shared context state. Writes here become operations that sync to every member and fold into the scope root. This is “the app’s state.”
PrivateNode-local state that never leaves the machine and never enters the scope root. For per-node caches and secrets.
Ordered indexA node-local, byte-ordered secondary index for range and prefix queries — a local accelerator, also never replicated.

Only the replicated layer produces operations. The other two are conveniences that stay on one node and have no effect on convergence.

Every node that applies the same operation must reach the same state, so execution must be a pure function of (prior state, input, injected host values). The runtime enforces this:

  • Writes buffer, then commit. A storage_write updates an in-execution buffer that later reads can see, but nothing is persisted until commit. If the guest traps or never commits, every change is discarded — execution is transactional.
  • Non-determinism is injected, not reached. A guest can’t read the clock or an RNG on its own; it asks the host via time_now / random_bytes, so those values are controlled and recorded rather than free.
  • Resources are bounded. Memory pages, stack depth, and the counts and sizes of logs, events, cross-calls, and storage keys/values are all capped. Exceeding a limit traps the execution — which, being uncommitted, changes nothing.
  • The method shape is fixed. Exported methods take no arguments and return nothing; input arrives through input() and results leave through value_return, so a method signature can’t smuggle in hidden state.

A successful commit yields an outcome: the return value, any logs and events, the cross-context calls to dispatch, the new state root hash, and an artifact describing the committed state changes.

That artifact is the bridge back to the core: its committed writes are packaged as data-plane operations (Put / Delete) in the context scope — each stamped with the executor’s identity, the scope’s current heads as parents, and a hybrid timestamp. From there they are exactly the same operations: appended to the DAG, folded into the root, and ready to broadcast.

The exact host ABI. All host functions live in the WASM env namespace. Pointers and lengths are u64 offsets into the guest’s linear memory; all integers are little-endian.

  • A buffer is passed as a 16-byte descriptor { ptr: u64, len: u64 } at a guest memory offset — the host reads len bytes from ptr.
  • Larger results are staged in host registers addressed by a u64 id. register_len(id) returns the length or u64::MAX if absent; read_register(id, dest_ptr) copies it into guest memory and returns 1 on success, 0 on a size mismatch.
  • A return value is written by the guest as a ValueReturn tagged union — a 1-byte tag (0=Ok, 1=Err) then, at offset 8, a 16-byte buffer descriptor.
FamilySignature
Inputcontext_id(reg: u64), executor_id(reg: u64), input(reg: u64), xcall_origin(reg: u64) -> u32
Registersregister_len(id: u64) -> u64, read_register(id: u64, dest: u64) -> u32
Replicated statestorage_read(key: u64, reg: u64) -> u32, storage_write(key: u64, val: u64, reg: u64) -> u32, storage_remove(key: u64, reg: u64) -> u32
Private stateprivate_storage_read / write / remove(…) — same shapes, node-local
Ordered indexstorage_index_set / remove / remove_prefix / scan / last(…) — node-local
Outputvalue_return(ptr: u64), log_utf8(ptr: u64), emit(ptr: u64), emit_with_handler(ptr, handler)
Commitcommit(root_hash: u64, artifact: u64)
Cryptoed25519_verify(sig: u64, pk: u64, msg: u64) -> u32
Host-providedtime_now(dest: u64), random_bytes(dest: u64)
Cross-contextxcall(ptr: u64)
Blobsblob_create() -> u64, blob_write / read / open / close(…), blob_announce_to_context(…)
Panicpanic(loc: u64), panic_utf8(msg: u64, loc: u64)

A storage read/write/remove returns 1 on hit and 0 on miss. The index scan result is encoded as u32 count, then for each pair u32 key-len ‖ key ‖ u32 value-len ‖ value.

LimitDefaultLimitDefault
memory pages1024 (64 MiB)stack size200 KiB
registers100register capacity1 GiB total
max register100 MiBlogs1024 × 16 KiB
events100 × 16 KiBxcalls100 × 16 KiB
storage key1 MiBstorage value10 MiB
blob handles100blob chunk10 MiB
method name256 Bmodule size20 MiB

Exceeding any limit traps the execution; because nothing has committed, the trap leaves state unchanged.

struct Outcome {
returns: Result<Option<Vec<u8>>, FunctionCallError>,
logs: Vec<String>,
events: Vec<Event>, // { kind: String, data: Vec<u8>, handler: Option<String> }
xcalls: Vec<XCall>, // { context_id: [u8;32], function: String, params: Vec<u8> }
root_hash: Option<[u8; 32]>,
artifact: Vec<u8>,
migration_witness: Option<Vec<u8>>,
}

We now know where data operations originate. The Write Path follows one out of the runtime: how an outcome becomes a signed, durably persisted, broadcast operation.