Sandbox & Inner Computer

Contract methods run inside a restricted SES compartment. The only chain-facing API available to that code is the InnerComputer (computer global): a read-only, fail-closed view of confirmed blockchain state.

For the full method reference, stabilizers, and confirmation rules, see Querying inside of a Contract.

Goals

  1. Determinism — If a query succeeds against a chain prefix, the same call must succeed with the same result on every extension of that chain.
  2. Fail closed — Transient facts (mempool, “not yet”, future heights) never become part of a valid transition.
  3. No silent soft-fail — Catching a thrown error does not clear invalidation; after the compartment returns, the host still rejects the transition if the evaluation was marked invalid.

Observation stability

For any InnerComputer method m and arguments args: if computer.m(...args) succeeds without invalidation against chain state b₁, then on any extension b₂ ⊇ b₁ the same call must succeed and return the same value.

Equivalently: every successful observation is invariant under future chain growth.

Confirmed locations (summary)

Most APIs require the referenced transaction to be in a block before the call may succeed:

Family Rule (summary)
sync / decode / load / getAncestors Start location/tx confirmed
first / prev Start rev confirmed; prev may return undefined at root
next Start and returned successor confirmed; no next → invalidate
last Start and result confirmed; tip must be spent confirmed (not a live unspent tip)
Block time/height/hash of a tx Tx confirmed
getBlockHash(height) Height ≤ tip; not future
getTXOs (+ aliases) Stabilizer: lteBlockHeight / blockHeight / blockHash

latest is not exposed inside contracts (the live tip is non-deterministic under chain extension).

Invalidation flow

  1. A query fails or observes a transient fact (or a direct policy rule rejects, e.g. future height).
  2. InnerComputer marks the current evaluation frame invalid and throws. There is no per-instance invalid flag and no contract-facing invalidation API. Each Db.eval / Modules.load runs under withEvalInvalidation (implementation lives in a dedicated eval-frame module):
    • Node: AsyncLocalStorage via process.getBuiltinModule('async_hooks') (no static node:async_hooks import, so browser bundles stay clean). Concurrent evaluations are truly concurrent and isolated by async context.
    • Browser: await-scoped stack with serialized root frames (no Promise patching under SES lockdown). Nested frames (e.g. Modules.load inside Db.eval) still nest; concurrent root evals queue so stack tops never cross-talk.
  3. The host sets the active observation client on the frame. Free-variable computer methods route to that client for the evaluation, so create-time SES bindings and the eval-time client share one observation identity. Invalidation always writes the active frame.
  4. The compartment may return after catch — the frame flag is not cleared until the host has checked it.
  5. The host accepts or rejects using only frame.invalid / frame.msg. If the compartment throws or returns after catch-and-continue, Db.eval still rejects when the frame is marked invalid.
  6. The in-compartment computer is a hardened query-only facade: public observation methods only (no isInvalid / errorMsg / resetInvalid, no internal client object, methods not replaceable).

console endowment (dev only)

Compartment globals include host console only when the client mode is dev or debug.

In prod, console is not endowed. Contract or module code that references console gets a ReferenceError and the evaluation fails. Do not use console in production smart contracts — logging is not part of the on-chain API, and endowing the shared host console is ambient authority (especially without SES lockdown, which runs in prod).

Use off-chain tooling and the outer Computer client for diagnostics.

Error message shape

Every invalidation path (policy _invalidate, missing/nullish _safeCall, and host rethrow from Db.eval) builds the public string with a shared formatter so callers always see exactly one copy of:

Accessing non-existent on-chain state inside a smart contract is forbidden.

A short reason may appear before that suffix, for example:

  • getBlockHash with future height is forbidden. Accessing non-existent on-chain state inside a smart contract is forbidden.
  • Transaction id … not found or not yet confirmed by sync. Accessing non-existent on-chain state inside a smart contract is forbidden.

The formatter is idempotent (already-suffixed strings are not doubled). Match with message.endsWith(...) (or equivalent). Do not expect a short policy reason alone without the standard suffix.

Client vs contract computer

  Outer Computer InnerComputer (computer in contracts)
Writes (new, broadcast, …) Yes No
Mempool / unconfirmed reads Often allowed (may return undefined) Forbidden → invalidate
latest Yes Not exposed
getTXOs without height/hash stabilizer Yes Forbidden → invalidate
Host console in compartment N/A dev / debug only (not in prod)
Concurrent evaluations Multiple clients/calls Isolated eval frames (ALS on Node; serialized roots in browser)

Security notes (what contracts cannot do)

  • Contracts cannot create or clear eval frames (withEvalInvalidation is host-only).
  • The endowment exposes query methods only — there is no isInvalid, errorMsg, resetInvalid, or resetGlobalInvalid for contracts to call or shadow.
  • The endowment is hardened so contracts cannot replace sync / first / … or reassign the prototype to hide invalidation.
  • Host reject decisions use the frame only.
  • In prod, contracts cannot use console (not endowed). Prefer no logging in on-chain code at all.

Practical implications

  • Confirm module deploys before contract load.
  • Confirm object revisions before history walks or escrow audits.
  • For terminal last checks, spend the tip (e.g. delete) and wait for confirmation.
  • Stabilize in-contract TXO queries with a historical height or block hash; empty result sets with a valid stabilizer are fine (wait for indexing if apps/tests expect a known object to appear).
  • Escrow / chess flows: cancel or settle, wait for confirmation, then withdraw / refund (cancel and withdraw cannot be one atomic observation of unconfirmed tip spend).
  • Do not ship contract methods that call console.* if they must run under mode: 'prod'.

See also