Lux Proposals
← All proposals
LP-0200Final

Status

Final. No backwards compatibility. No flags. No replay.

Activated at the genesis of the new final Lux network: **2025-12-25 16:20

Pacific (unix 1766708400)**. Predates every block. The pre-Quasar

Edition Lux network (2020–2025) is a separate network and is out of

scope. There is no migration path because there is nothing to migrate —

the new network starts from genesis with this stack.

Abstract

The Lux ZAP Stack is a decomplected blockchain architecture. A

transaction is a ZAP buffer when it crosses the wire; it is the same

ZAP buffer when it sits in the mempool; it is the same ZAP buffer when

it lands in state; it is the same ZAP buffer when ZapDB streams it to

an age-encrypted snapshot. One byte stream. No codec.Manager runtime

registry. No reflection. No Marshal. No Unmarshal. No Encode. No

Decode. No Serialize. The buffer is the value.

Each layer of the stack does one job. ZAP frames the bytes. ZapDB

stores the bytes. Schemas describe the offsets. sha256.Sum256(buf)

yields stable identity. TupleHash256 with SP 800-185 framing yields a

cryptographic transcript. wire.SignSecp256k1 produces a signed-tx

envelope by emitting a credential window over the unsigned bytes.

Per-schema Verify() reads offset-indexed fields. Executor dispatch

keys on the TxKind byte. A block is a ZAP frame containing tx frames.

Consensus composes BLS + Pulsar + Corona + Magnetar legs as opaque

ZAP byte strings inside a QuasarCert. Each of these is a separate

concern, in a separate layer, with no braiding.

The reason this is elegant is not aesthetic. It is operational: the

cost of every braided concern in the legacy Avalanche-derived stack

(re-marshalling between mempool and state, re-encoding for snapshot,

parsing on every consensus-message hop, registering codec versions at

runtime) is paid every block. Removing the braids removes the cost.

Measured wins span 3× to 13× per operation. Composed across a running

luxd validator, the wins compound.

The 10-Layer Model

| # | Layer | One Job | Implementation |
|---|---|---|---|
| 0 | ZAP frame | binary wire envelope (magic + version + object) | luxfi/zap (LP-022) |
| 1 | ZapDB | LSM KV consuming ZAP bytes; incremental snapshots; age-encrypted backups | luxfi/zapdb (Badger fork, !grpc build tag, pb_zap.go native encoders) |
| 2 | Schemas | TypeKind + ShapeKind discriminator + offset map per type | pkg/wire/zap/schemas.go per repo |
| 3 | Identity | sha256.Sum256(buf) for stable IDs | callsites, no central impl |
| 4 | Transcript | TupleHash256 / cSHAKE256 with SP 800-185 framing — cryptographic injectivity over typed fields | consensus/protocol/quasar/round_digest.go, qblock.go::TranscriptHash |
| 5 | Signature | wire.SignSecp256k1(unsignedBytes, signers) → ZAP SignedTx{unsignedBytes, creds} | luxfi/utxo/wire/sign.go |
| 6 | Validator | per-schema Verify() reads offset-indexed fields, no codec dispatch | per-type validators in each VM |
| 7 | Executor | dispatch on TxKind byte (first byte of envelope); no Visit pattern; no type switch | per-chain executor with kind-byte registry |
| 8 | Block | block bytes are a ZAP frame containing a list of tx bytes (also ZAP frames); block ID = sha256(block.Bytes()) | per-chain block schema in consensus/pkg/wire/zap/qblock.go |
| 9 | Consensus | LP-182 QuasarCert composes BLS + Pulsar + Corona + Magnetar legs as opaque ZAP byte strings; verify is per-leg dispatch | consensus/protocol/quasar/ |
| 10 | P2P transport | QUIC streams carrying ZAP frames; mDNS for LAN discovery; Kademlia DHT for content-addressable chunks | LP-201 (sibling LP) |

Each row is one job. Each row consumes the layer below and produces

the layer above as a byte slice. No layer reaches across.

Decomplected Concerns

Ten things that the legacy Avalanche-derived stack braided together,

unbraided here.

1. Wire format ↔ in-memory type ↔ on-disk record

Legacy: three Go structures — wire DTO, runtime struct, storage row —

with marshal/unmarshal hops between them. Every read parsed twice,

every write encoded twice. ZAP Stack: one buffer. The wire is the

struct is the record. Accessors are offset reads. Bytes() returns

the buffer. ZapDB stores the buffer as-is.

2. Signature payload ↔ transcript ↔ wire

Legacy: a signed transaction had a marshal pass for the signature

payload, a separate marshal pass for the consensus transcript, and a

third marshal pass for the gossip envelope. ZAP Stack: one byte

stream feeds three orthogonal operations — sha256 for identity,

TupleHash256 over typed offsets for the consensus transcript, and

ecdsa(sha256(unsigned_bytes)) for the signature. Each operation

reads the same buffer at different offsets. No re-encoding.

3. Tx envelope ↔ kind dispatch ↔ executor visitor

Legacy: the Visit pattern — tx.Visit(v UnsignedTxVisitor)

required every executor to type-switch on Go interface assertions

inside generated code. ZAP Stack: the first byte of the unsigned

envelope is TxKind. The executor is a map[TxKind]Handler lookup.

No interface polymorphism, no generated visitors, no type switch.

Value-based dispatch on a buffer byte.

4. Block ↔ proposervm wrapper ↔ chain-VM block bytes

Legacy: proposervm wrapped every chain-VM block in its own envelope

with its own signature, and the wrapped bytes had to be parsed at

every consensus hop. ZAP Stack: the proposer's signature is a leg of

the QuasarCert (LP-182), not a separate envelope. The block is the

block. Validators verify the cert's BLS leg to authenticate the

proposer.

5. Mempool ↔ chain ↔ network gossip

Legacy: a tx had three representations — gossip wire, mempool

in-memory struct, chain bytes — with marshal hops between them. ZAP

Stack: the same buffer that arrives over the wire goes into the

mempool by reference, lands in a block by reference, and persists to

ZapDB by reference. One buffer, three places.

6. Validator ↔ delegator ↔ L1-staker

Legacy: three separate state tables with three separate marshalled

structures and three separate code paths for stake accounting. ZAP

Stack: one ZAP wire format with a StakerKind discriminator (LP-189

platformvm state). One table. One executor path. The kind byte

chooses the fee rules and the reward schedule.

7. P-chain ↔ X-chain ↔ EVM ↔ chains-VM primitives

Legacy: each VM had its own copy of UTXO, OutputOwners,

Credential, BaseTx. ZAP Stack: luxfi/utxo/wire/ is the single

source of truth. P-chain (LP-023), X-chain (LP-184), EVM internal

(LP-185), and the 12 chains VMs (LP-186) all consume the same nine

migrated fxs (secp256k1fx, nftfx, propertyfx, ed25519fx,

mldsafx, slhdsafx, secp256r1fx, pqfx, vrffx) through their

per-fx wire.go bridges.

8. Consensus transcript ↔ wire envelope

Legacy: the consensus transcript was a separately marshalled

structure that had to be re-derived from the wire envelope at every

hop. ZAP Stack: the transcript function reads typed fields directly

from the ZAP buffer (consensus/pkg/wire/zap/qblock.go::TranscriptHash).

The wire is the input to the transcript. No re-derivation.

9. Codec.Manager ↔ Sign ↔ Verify

Legacy: codec.Manager was a runtime registry of versioned

type-codec pairs; every Sign and Verify call paid a registry lookup

and a reflective marshal. ZAP Stack: compile-time schemas declared in

pkg/wire/zap/schemas.go per repo. Sign and Verify read offsets

directly. No runtime registry. No reflection. No version negotiation

at runtime — versioning lives in the schema kind byte.

10. Genesis ↔ block ↔ state ↔ gossip

Legacy: the genesis file format, the block format, the state-row

format, and the gossip format were four distinct codecs that all had

to be kept in sync. ZAP Stack: one wire codec across all of them. A

genesis is a ZAP buffer with a GenesisKind discriminator. A block

is a ZAP buffer containing a list of tx buffers. A state row is a

ZAP buffer. A gossip message is a ZAP buffer. Same parser, same

writer, same identity function.

Performance Characteristics

Measured on luxd microbenchmarks against the legacy linearcodec

implementation. Numbers are wall-clock per operation and allocation

count from go test -bench.

| Op | Legacy (linearcodec) | ZAP Stack | Win |
|----|---|---|---|
| Parse small tx | 129 ns/op, 2 allocs | 36 ns/op, 1 alloc | 3.6× |
| Parse complex tx | 1008 ns/op, 6 allocs | 75 ns/op, 1 alloc | 13.4× |
| Mempool insert | parse → struct → re-marshal | parse → buffer ref | ~5× |
| State write | unmarshal → mutate → marshal | wrap → mutate → write buffer | ~3× |
| Snapshot stream | re-encode for backup | direct buffer stream | ~10× (write-amp gone) |
| Consensus round Corona n=64 | 14s serial-aggregate (incorrect) | 356–440ms parallel (correct) | −97% |

These compose. A luxd validator running consensus rounds while

admitting transactions and writing state sees the per-op wins

compound at every layer. The Corona row deserves a note: the legacy

serial-aggregate implementation was a bug, not a baseline; the

parallel implementation is the honest comparison point. The win is

real.

Why "elegant"

One wire, one identity, one transcript

Three concerns, three layers, no braiding. The wire is the byte

stream. Identity is sha256.Sum256(buf). The transcript is

TupleHash256 over typed offsets. Each is independently complete.

None reaches into the others. Adding a new transcript function does

not touch the wire. Changing the identity hash does not touch the

transcript. The layers do not know about each other.

TxKind dispatch ≠ interface polymorphism

A Visit-pattern type switch is a place — the code that names every

concrete type and dispatches on the runtime type tag. A TxKind byte

is a value — the dispatch table is map[TxKind]Handler, the handler

is selected by lookup, and the executor never names a concrete tx

type. Adding a tx kind is one entry in the registry. Removing one is

one deletion. The executor does not change.

The buffer IS the value

Rich Hickey on places versus values: a place is a slot that can hold

different things over time; a value is a thing that does not change.

ZAP-native is values all the way down. The buffer that arrives over

the wire is the same buffer in the mempool, the same buffer in the

block, the same buffer in state, the same buffer in the snapshot.

The identity is a function of the buffer. The transcript is a

function of the buffer. The signature is over a window of the

buffer. Nothing mutates. Nothing re-encodes. The buffer is the

value.

What's NOT yet decomplected

Honest accounting of remaining implementation work. The activation

marker stands; these items are migrations of legacy code paths to the

already-decomplected architecture, not architectural blockers.

None of the above mutate the architecture. The architecture is the

10-layer model. These are residual migrations.

Migration ledger

What landed across the new final Lux network's pre-genesis sprint:

Future work

Activation marker


activates: 2025-12-25T16:20:00-08:00
activates-unix: 1766708400

Same marker as every wire LP in the new final Lux network. Set once,

does not move.

Cross-references