Every proof-of-stake chain in the Ouroboros lineage freezes one number at genesis: the active-slot coefficient that governs how often blocks appear. Tetra is a from-scratch Layer-1 built around a different idea — that block cadence should be a consensus variable, recomputed every block by a bounded feedback controller from the chain’s own history, carried in the header, and re-verifiable by any node. Around that core I built the full stack: hand-implemented cryptography, an eUTXO multi-asset ledger, JavaScript smart contracts, an indexer, an explorer, a wallet, a benchmarking harness, and — the part I’m proudest of — two complete node implementations that agree byte for byte.
| Role | Protocol design, whitepaper, and both node implementations |
| Stack | Kotlin 2.0 + Spring Boot (reference node), near-std-only Rust (active node), Axum + ScyllaDB + PostgreSQL (explorer), React 19 + D3 (protocol site) |
| Scale | ~64k LOC Rust, ~42k Kotlin, ~40k TypeScript; 302 Rust + 174 Kotlin tests |
| Evidence | Live geo-devnet sync: 2,510/2,510 blocks accepted, zero rejections (2026-07-10) |
| Whitepaper | “A Self-Pacing Proof-of-Stake Protocol with Consensus-Embedded Cadence Control”, v0.3 draft — tetra-protocol.fly.dev |
The problem
Fixed cadence is a real limitation — when network conditions drift, the one parameter that governs block frequency can never respond — but the deeper engineering problems are trust and determinism.
Trust: a new chain written by one person has to answer why anyone should believe the consensus rules are implemented correctly. Peer review helps a specification; it does nothing for the ten thousand lines that implement it.
Determinism: I earned this lesson the hard way. Early devnets fractured into ten to fourteen competing chain tips, and the root cause was wall-clock reads inside chain-selection logic — honest nodes evaluating the same fork at different moments reached different conclusions. Nondeterminism anywhere in a consensus path eventually becomes a fork. The protocol’s design principles all trace back to that incident.
Design decisions
Consensus-embedded cadence control
The active-slot coefficient f is recomputed each block by a bounded multi-timeframe feedback controller reading only chain history — no oracles, no operator knobs. The new value ships in the block header, so every validator re-derives and checks it; a producer who lies about the coefficient produces an invalid block. An earlier design (a validator-count-based proposal) was scrapped in favor of this time-based mechanism precisely because chain history is the one input every node provably shares.
The protocol’s core numbers:
| Parameter | Value |
|---|---|
| Target mean block time | 6 seconds (1 slot = 1000 ms) |
| Epoch | 432,000 slots = 5 days |
| Settlement depth | 1,038 blocks |
| Supply cap | 21,000,000 — enforced by block validation |
| Halving period | 292 epochs ≈ 4 years |
| Fork tips retained | capped at 2 |
The whitepaper proves a full-stake fixed point (f = 1/τ) and a computable trajectory envelope for the controller — the claim is not “it works in tests” but “here is the math, check it.”
Determinism as a hard rule, enforced by fork choice
The multi-tip incident led to a closed-form, O(1) fork-choice function — an exact score every node computes identically:
score = size · 121 + density · 0.7 · 100 + 1/(freq + 1) · 0.5 · 100
candidates ordered by: height DESC, timestamp DESC, hash ASC
tie-break: lexicographically smaller hash wins
Height dominates; density and frequency refine; the hash tie-break guarantees that two honest nodes given the same tips always choose the same winner — no clocks, no randomness, no iteration over history. Reorgs are counted whenever a node switches to a non-descendant tip, so divergence is observable the moment it happens rather than days later.
A frozen oracle and a byte-exact port
I wrote the node twice, deliberately. The Kotlin/Spring Boot node came first and is now frozen — a read-only correctness oracle whose consensus behavior is the definition of the protocol. The active Rust node is a byte-exact port, verified check by check against committed golden vectors generated from the Kotlin implementation, down to a documented wire quirk (millisecond-rounded timestamps retried at t−1 ms).
The Rust node is near-std-only: SHA-256/512, Ed25519, ECVRF (RFC 9381, suite ECVRF-EDWARDS25519-SHA512-Elligator2), edwards25519 field arithmetic on 5×51-bit limbs, an RFC 8259 JSON parser, and the HTTP server are all hand-rolled and validated against RFC 8032 and FIPS 180-4 test vectors. The only external dependencies are rustls and webpki-roots for outbound HTTPS. Two independent implementations that must agree, with the disagreement detector automated, is the strongest correctness argument a solo protocol can make.
Leader election itself is a stake-weighted VRF lottery: a lottery anyone can check, a winner no one can predict. Hashing is domain-separated Merkle throughout — leaf, node, root, and size tags rather than naive concatenation — and the block hash is the Merkle root over a 13-element header list, so header fields are individually provable.
Local validation, approachable contracts
The ledger is extended-UTXO with datums and native multi-asset: a transaction depends only on the outputs it consumes, so validation is local, parallel, and order-independent — no global mutable state, and amounts are arbitrary-precision (never floats). Smart contracts are plain JavaScript — policy(context, redeemer, datum) — executed in a deterministic GraalVM sandbox for spending, minting, and staking policies. If you can write a function, you can write a contract; a purpose-built DSL is a barrier this protocol refuses to charge.
Architecture
┌────────────────────────────────────────────────┐
│ CHAIN PLANE │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ │
gossip ───────┼─►│ Kotlin ref node │◄─►│ Rust node :8480 │ │
:8081-:8085 │ │ (FROZEN oracle) │ │ active, near-std │ │
dev fleet │ │ Spring Boot + PG │ │ hand-rolled HTTP │ │
│ └───────────────────┘ └─────────┬─────────┘ │
└────────────────────────────────────┼───────────┘
│ sync: header-locator walk,
│ by-hash pull, range fallback
┌────────────────────────────────────┼───────────┐
│ ECOSYSTEM ▼ │
│ ┌──────────────┐ ┌────────────────────────┐ │
│ │ tetra-indexer│ │ tetra-explorer :8092 │ │
│ │ Axum :8091 │ │ hexagonal: domain / │ │
│ └──────┬───────┘ │ application / infra │ │
│ │ └───────┬────────┬───────┘ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌──────────┐ ┌───────────┐ │
│ │ PostgreSQL │ │ ScyllaDB │ │PostgreSQL │ │
│ │ (indexer) │ │ immutable│ │ derived │ │
│ └────────────┘ │ history │ │ state │ │
│ └──────────┘ └───────────┘ │
│ tetra-bench / tetra-eval · Prometheus :9090 │
│ Grafana :3000 · protocol site (React 19 + D3) │
└────────────────────────────────────────────────┘
The explorer’s dual store is a deliberate split: ScyllaDB holds immutable history, PostgreSQL holds derived UTXO/address/stake state, and every table is partitioned by node — each followed node is an independent “cell” whose chain, including its forks, is indexed separately. Reorgs mark history orphaned (never deleted), rewind derived state, and replay from the fork point. Operations run on Docker fleets — five-node dev profiles through geo-distributed deployments — and the Rust node ships an htop-style terminal dashboard with braille sparklines, producer leaderboards, live peer RTT, fork tips, and cadence gauges.
The life of a block
per slot per block parallel 1,038 blocks
┌───────────┐ ┌────────────┐ ┌─────────────┐ ┌───────────┐
│ ELECT │ │ PACE │ │ VALIDATE │ │ SETTLE │
│ stake- │ │ controller │ │ eUTXO local │ │ O(1) fork │
│ weighted ├─────────►│ recomputes ├─────────►│ + parallel ├─────────►│ choice · │
│ ECVRF │ │ f from │ │ JS policies │ │ hash tie- │
│ lottery │ │ history → │ │ in GraalVM │ │ break · │
│ RFC 9381 │ │ into header│ │ 21M cap │ │ orphans │
└───────────┘ └────────────┘ └─────────────┘ │ replayed │
└───────────┘
proof anyone value anyone no ordering same answer
can verify can re-derive games on every node
Nothing in this pipeline consults a wall clock. Every stage produces something another node can independently recompute — that property, applied uniformly, is the whole design philosophy.
Evidence
On 2026-07-10 the Rust node performed a full sync of a live geo-distributed devnet of 18 Kotlin validators: 2,510 of 2,510 blocks accepted with zero rejections — every ECVRF proof, signature, body hash, and block hash recomputed and matched. Observed cadence: p50 of 4.9 seconds against the 6-second target, p95 of 17 seconds, within the derived gate. The test estate totals 302 Rust and 174 Kotlin tests with coverage gates (≥80% line coverage) on the pure consensus and store modules.
A detail I keep because it’s the kind that matters: the repo includes a custom Rust agent-governance harness that computationally enforces the Kotlin freeze — write-permission policy, secret scanning, and named verification checks run in CI, so “the oracle is frozen” is a machine-checked invariant, not a convention.
Status and limits
Stated plainly, as the whitepaper does: production scale (1,000+ validators, WAN mesh) is specified, not demonstrated; Praos-style security (common prefix, chain growth, chain quality) under the adaptive coefficient is stated as a conjecture, not a proven theorem; the whitepaper is a v0.3 draft; one known defect is open (final convergence fails in the geo8 scenario); and chain selection has a measured O(height) degradation that the bench harness exists to track. The protocol site publishes the measurements, passing and failing alike.
What this demonstrates
Tetra is protocol design, applied cryptography, and cross-language systems engineering in one artifact: a novel consensus mechanism with a written analysis, primitives implemented from specifications and locked with golden vectors, a hard determinism rule extracted from a real production incident, and a dual-implementation discipline that turns “trust me” into “check for yourself.” If your problem involves consensus, determinism, or verifying that two systems really do the same thing, book a consultation — or start with the whitepaper.