Sigma-Anchored Node Test Apparatus — a cross-implementation conformance test suite for Ergo consensus.
Built in the open, slice by slice. The design takes shape from working deliveries rather than a big up-front spec — see SPEC.md for the current architecture + roadmap, and BOOTSTRAP.md for the rationale behind each call.
Language-agnostic conformance test vectors + thin per-implementation
runners + CI, so multiple independent Ergo implementations can be checked
against the same canonical inputs and expected outputs — the way Ethereum's
execution-specs (the executable
spec + test framework, formerly execution-spec-tests / EEST) lets
geth / besu / nethermind / reth prove consensus-equivalence.
The guiding principle is "the wire is the spec": a vector is raw serialized bytes in → expected output out, since every implementation already parses the wire format. Expected outputs are anchored to canonical oracles, never to any single implementation:
- block validity → the chain — a block on mainnet is valid by definition;
- fine-grained eval outputs (value, cost, reduced sigma-tree) → the JVM reference
node (ergo-core /
sigma-state), the de-facto spec.
Four tiers:
- Wire tier — serialization: bytes ⇄ structure (constants, boxes, trees, txs,
headers). The broadest — every wallet/SDK serializes (ergots, sigma-rust, scorex,
Fleet, …); and a
boxIdis the hash of serialized bytes, so it's squarely consensus. - Eval / transition tier — operation-level (ErgoTree + context → typed value, with cost). Run by the consensus libraries, no full node required.
- Transaction tier — library-decidable tx validity given full inputs (script-verify
- conservation / tokens / min-value / cost). No full node required; blessed by
ergo-core validateStateful.
- conservation / tokens / min-value / cost). No full node required; blessed by
- Block tier —
block H → valid? / state-root. Run by full nodes.
The eval tier is closed and scaled, and the conformance loop is already surfacing genuine cross-implementation divergences — which is exactly its job. What runs today:
- ✅ A blessed eval corpus — 2,143 entries across 155 vector files: 2,026 produced by
the JVM reference interpreter (
sigma-state) from its own language specification, plus 117 authored gap-fillers (oracle-blessed, never spec-copied); version-split into v5 (1,798 entries — the cumulative v5/mainnet method surface) and v6 (345 — the v6 new-feature surface). Each entry isErgoTree bytes (+ input) → typed value + raw JIT cost, committed with the(activated, ergoTree)version it was blessed under. - ✅ A runner-agnostic orchestrator —
./conform(presence-as-state overrunners/*/, one shared comparator, a per-runner per-slice 🎁/🪨 table). Five runners wired today: Rudolph (the JVM reference — the all-🎁 control that blessed the corpus), Dasher (the pure-TSergotslibrary,ts-runner/), Blitzen as two submodules pinningsigma-rustat upstreamdevelop(value-only) and theergo-node-integrationfork (--features jit-cost), and Comet (the pure-TS Fleet SDK — wire tier only). Each is graded against the JVM-blessedexpected— the runner is SANTA's; the implementation under test is a dependency. - ✅ Live results — the loop is surfacing real divergences. Dasher is fully green on
the v5 spec corpus (1,757 / 1,757) — the 52 healed AvlTree-typed entries initially
surfaced a SANTA harness encode gap (the result-encode bridges lacked an AvlTree arm;
x.R9[AvlTree].getevaluates correctly in every conformer), fixed same-arc. Blitzen shows the suite working —sigma-rust'sergo-node-integrationfork is green suite-wide except two cost entries (the deserialize-bearing pair, blessed at production substituted-constant cost per the contract rule; eni's eval harness charges the lazy form — routed). Everything else — eval + wire + transaction, value and cost — is green (every divergence the suite surfaced is fixed in the fork, latest theBox.getRegmethod-id and HOF FunDef/currying findings), while plain upstreamdevelopmisses 10 v5 values the fork already fixed, plus the v6 surface. A 🪨 is the suite doing its job, never silenced. - ✅ A frozen runner contract (
docs/contract/runner-contract.md)- a JVM blesser, the JVM reference runner (Rudolph), and a harness. A runner is
total: it emits one faithful outcome for every entry — value + cost on success,
else a coarse tag (
errored/not-implemented/panicked) — and never drops, hides, or aborts the run on one. A conformer's scope is chosen on the input side (it runs the vector subset it claims; dasher's manifest declares version ≤ v6). A divergence is the deliverable — surfaced and routed, never silenced; a red gate means the suite is working.
- a JVM blesser, the JVM reference runner (Rudolph), and a harness. A runner is
total: it emits one faithful outcome for every entry — value + cost on success,
else a coarse tag (
- ✅ Machine-checkable gates — a JSON-Schema validator over the whole corpus (155 eval + 4 wire + 4 tx) and an end-to-end conformance gate. The CI seed.
- ✅ Wire tier live —
santa-wire/v1byte-round-trip vectors, 213 entries (Constant178 ·Box11 ·SigmaBoolean7 ·Transaction17), JVM-canonicalized from ergots'fixture-gen+ Fleet's_test-vectorsseeds. Rudolph + Blitzen 213/213; Dasher 196 (no tx serializer — growth ledger); Comet 185 (Fleet's honest gaps, recorded as findings). Contract:docs/contract/runner-contract-wire.md. - ✅ Transaction tier live —
santa-transaction/v1schema; 4 captured vectors (vectors/transaction/v6/captured/), each JVM-blessed viaergo-core 6.0.2.1 validateStateful. Conformer stances: Rudolph out (oracle-tautological + keeps ergo-core out of CI) · Blitzen-enivalid 4/4 · cost 4/4byte-exact (the initial bless surfaced 3 genuine cost divergences — decomposed, routed, fixed in the fork, re-graded exact: the tier's first divergence→fix→convergence loop) · Blitzen-developvalid 0/4(upstream bugs; the bigint-downcast seed exposes the tree-version bug eval cannot catch) · Dasher4 not-implemented(growth ledger) · Comet out-of-scope (wire-only; Fleet has no verifier). Contract:docs/contract/runner-contract-transaction.md.
Still greenfield, and where help is most wanted (see below):
- the block tier (chain-blessed block vectors) and the tx-tier authored reject arm;
- more independent runners — the full nodes (
sigma-rustis now wired, as Blitzen); - the reject arm — authored negative / mutation vectors (rejected for the right reason); and a full CI gate.
SPEC.md umbrella spec — architecture, tiers, contracts, roadmap, glossary
BOOTSTRAP.md design rationale + decision log (the *why*)
docs/contract/ the frozen runner I/O contracts (eval · wire · transaction)
docs/specs/ per-phase subspecs
docs/findings/ recorded cross-implementation divergences
schema/ JSON Schemas for vectors + actuals, and the validator
vectors/eval/ the canonical eval corpus — the "nice list" (v5/ and v6/)
vectors/wire/ wire-tier round-trip vectors
vectors/transaction/ transaction-tier captured vectors (v6/captured/)
jvm-blesser/ Scala: the blesser, the JVM reference runner (Rudolph), the harness
ts-runner/ Dasher — the ergots runner + the conformance gate
runners/ per-conformer dirs (rudolph · dasher · blitzen-develop · blitzen-eni · comet)
conform the runner-agnostic orchestrator — runs every runner, prints the table
README.md this file
The transaction blesser is env-gated and never runs in CI — committed vectors are the reproducible artifact. To re-bless (e.g. to add new seeds):
- Clone
ergo-node-buildat tagv6.0.2.1. - From that clone:
sbt "avldb/publishLocal" "ergoWallet/publishLocal" "ergoCore/publishLocal"— publishesergo-core 6.0.2.1to~/.ivy2/local. - From
jvm-blesser/:SANTA_TX_BLESSER=1 sbt -batch "testOnly santa.CapturedTxTest"— stages blessed JSON underjvm-blesser/target/tx-vectors/. - Copy the staged files into
vectors/transaction/v6/captured/and commit.
The bundled jvm-blesser/src/test/resources/chain-testnet.conf is pinned by the
transaction runner contract and must
not be changed without re-blessing.
SANTA is meant to be community-owned conformance ground: the more independent Ergo implementations run the same vectors, the more the vectors are worth. Help is genuinely wanted — especially:
- Implementations under test — wire a runner for your Ergo implementation
(TypeScript, Rust, Scala, or anything that parses the wire format) against the
runner I/O contract. It's a thin adapter:
vector in →
{value, cost, error}per entry. - Vectors — more eval-tier operations; wire-tier serialization vectors; captured-block vectors for the block tier; authored mutation / reject vectors.
- Design — the vector schema, the runner I/O contract, CI topology.
The eval-tier contract is frozen, but the wider design is still taking shape, so
a conversation beats a big PR — read SPEC.md + BOOTSTRAP.md,
look at jvm-blesser/ and ts-runner/, and open an issue to talk through where you'd
like to plug in.
- Ergo — the protocol under test.
sigma-rust— Rust consensus library; a convenience differential target (never the oracle).execution-specs— Ethereum's executable spec + test framework (the formerexecution-spec-tests/ EEST, consolidated in 2025); the project SANTA's structure is modeled on.
- Fleet SDK — its serializer test vectors
(
packages/serializer/src/_test-vectors) are vendored into SANTA's wire tier: harvested and re-anchored through the JVM oracle, so each harvest doubles as a JVM-vs-Fleet differential pass (178 constants, 17 signed transactions, 7 boxes). - ergots'
fixture-genwire fixtures are vendored too (boxes + sigma-booleans). Box round-trips from ergots and Fleet share one slice, each entry tagged by its framework.
MIT.