Documentation Runtimes & cursors

Runtimes & cursors

A runtime is one process hosting one nest or many. A cursor is a single chain’s follow position - the thing that reads the tip, tracks finality, and handles reorgs. The relationship between them is the whole story.

In 2.0 the runtime also owns tenancy: a mount is (tenant, nest identity), two tenants mounting the same nest share one dataset, and a single-tenant runtime is simply N=1 with a default tenant nobody has to type. There used to be a separate roost concept wrapping all this; it was retired, and the runtime absorbed it.

The single-cursor law

A cursor is single-chain, single-writer, one observable failure boundary. One cursor tracks exactly one chain’s canonical history - never two. Chains reorg, finalize, and advance on independent clocks, so sharing a cursor between two of them is incoherent, not merely inadvisable. This law is non-negotiable: nuthatch never multiplexes two chains behind one cursor.

One chain, many nests

Nests on the same chain co-located in one runtime share one cursor: a single getLogs per window, fanned out to the owning nests. So N nests cost roughly one nest’s worth of RPC chatter - the density win. Per-nest tables stay byte-identical to running each nest solo, because the same per-window code runs either way.

Isolation is by construction: each nest keeps its own dataset directory (data/<nid>/ - its own hot store and views), so one nest’s bad view or runaway factory can’t touch another’s data. Sealed segments are the deliberate exception: they are content-addressed and shared in segments/, so two nests that decode the same contract hold one byte-identical copy between them rather than two.

Many chains, one runtime

A runtime can also span multiple chains - one Base nest and one Arbitrum nest in a single process - by running one isolated cursor per distinct chain. Each cursor has its own RPC source, tip-follow loop, finality view, reorg boundary, and hot-store partition. A reorg on one chain touches only that chain’s cursor; another chain’s data is left byte-identical.

# mounts.toml - multichain form: declare each chain's RPC, nests carry their own chain.
# Written by `nuthatch migrate` and kept in step by the runtime; you do not hand-write it.
[runtime]
name = "my-runtime"

[[mounts]]
alias = "base-pool"
nid = "9f2c…"

[[mounts]]
alias = "arb-pool"
nid = "4a71…"

[[chains]]
chain = "base"
chain_id = 8453
rpc_urls = ["https://base-rpc"]

[[chains]]
chain = "arbitrum-one"
chain_id = 42161
rpc_urls = ["https://arb-rpc"]

A capability, not a mandate. One-chain-per-runtime stays fully valid and is the simplest default. Multichain is there for operators who want the density; the runtime enables the option, it never forces co-location.

The footprint budget

The footprint budget is per active-chain cursor: ≤2 GB RAM. A single-chain runtime is one cursor (≤2 GB); a multichain runtime’s total is the sum of its cursors. Each chain’s cursor is held to the budget independently - a cursor whose projected footprint would exceed it is refused before it starts. Density is RAM-bounded, not free.

Failure boundaries

Each cursor is one observable failure boundary. One chain stalling or reorging cannot harm another chain’s nests; the runtime fate-shares its serving with every cursor, so a dead cursor fails the whole runtime loudly rather than serving stale data as if healthy.

Next