All chain-VM internal wire surfaces in ~/work/lux/chains/ are ZAP. The
Go struct IS the wire — there is no codec, no marshal step, no encode
or decode. A block, tx, vertex, or any other on-chain envelope is a
ZAP buffer; accessors are offset reads on that buffer; Bytes()
returns the buffer.
Activated at the genesis of the new final Lux network:
2025-12-25 16:20 Pacific (unix 1766708400).
There is no other wire on this network. No legacy. No backwards
compatibility. The pre-Quasar Edition Lux network (2020–2025) is a
separate network and is out of scope.
This LP is the umbrella for chain-side VMs. The primary-network wires
are covered by sibling LPs:
type BlockWire struct {
msg *zap.Message // the wire buffer itself
obj zap.Object // typed window into msg
}
func (b BlockWire) BlockHeight() uint64 { return b.obj.Uint64(OffsetBlock_BlockHeight) }
func (b BlockWire) Bytes() []byte { return b.msg.Bytes() }
There is no Marshal. There is no Unmarshal. There is no Encode.
There is no Decode. There is no Serialize. Parse(b) wraps b in
a typed accessor — it does not copy and does not decode.
Build(...) writes fields into a buffer at known offsets and the
buffer is the wire bytes.
This is the Cap'n-Proto / FlatBuffers invariant: **wire format is the
in-memory representation**.
Each VM owns its wire/ subdir under ~/work/lux/chains/<vm>/wire/.
Each file in that directory defines one wire-envelope schema with:
Kind constant (1-byte discriminator stored at offset 0).Offset<Schema>_* constants block defining the fixed-section layout.<Schema>Wire zero-copy accessor struct.<Schema>Input constructor input struct.New<Schema>(in) constructor returning <Schema>Wire.Wrap<Schema>(b) parser returning <Schema>Wire plus error on:zap.Parse fails),Version2 (ErrWrongSchemaVersion),ErrWrongKind).0xC0 | aivm | KindVertex / KindQueryTx / KindInferenceTx / KindTaskResult | aivm/wire/{vertex,query_tx,inference_tx}.go |0xC1 | bridgevm | KindBlock / KindBridgeRequest / KindGenesis | bridgevm/wire/{block,bridge_request,genesis}.go |0xC2 | dexvm | KindBlock / KindOrderTx / KindCancelTx / KindSwapTx / KindLiquidityTx / KindMatchTx | dexvm/wire/{block,order_tx,swap_tx}.go |0xC3 | evm (chains-side) | KindPluginEnvelope / KindChainGenesis | evm/wire/{plugin_envelope,chain_genesis}.go |0xC4 | graphvm | KindBlock / KindSchemaUpdateTx / KindIndexUpdateTx / KindGraphMutationTx / KindQueryResultTx | graphvm/wire/{block,schema_update_tx,graph_mutation_tx}.go |0xC5 | identityvm | KindBlock / KindIdentityTx / KindCredentialTx / KindRevocationTx | identityvm/wire/{block,identity_tx,credential_tx,revocation_tx}.go |0xC6 | keyvm | KindBlock / KindKeyTx / KindKeyRotationTx | keyvm/wire/{block,key_tx,key_rotation_tx}.go |0xC7 | oraclevm | KindBlock / KindOracleUpdateTx / KindOracleQueryTx / KindAggregationTx | oraclevm/wire/{oracle_update_tx,aggregation_tx}.go |0xC8 | quantumvm | KindBlock / KindQuantumTx / KindPrecompileTx / KindQuasarWitness | quantumvm/wire/{block,quantum_tx,precompile_tx}.go |0xC9 | relayvm | KindOpenChannelTx / KindCloseChannelTx / KindSendPacketTx / KindReceivePacketTx / KindRelayReceiptTx | relayvm/wire/{channel_tx,packet_tx,receipt_tx}.go |0xCA | thresholdvm | KindBlock / KindOperation / KindManagedKey / KindCrossChainMPCRequest | thresholdvm/wire/{block,operation,managed_key,cross_chain_request}.go |0xCB | zkvm | KindBlock / KindTransaction / KindUTXO / KindPrivateAddress / KindVertex | zkvm/wire/{block,transaction,utxo,private_address,vertex}.go |Schema-ID values are the VM namespace bucket. The 1-byte Kind
discriminator inside each VM is local — i.e. KindBlock=1 inside
bridgevm/wire is distinct from KindBlock=1 inside graphvm/wire,
because they live in different packages with different schema tables.
External cross-VM tooling that needs to disambiguate uses the schema
ID as the namespace prefix.
Every chain-side wire envelope's fixed section starts with a 1-byte
Kind at offset 0. Wrap<Schema> verifies the discriminator before
returning a typed accessor — rejects cross-kind confusion (e.g. a
Block buffer Wrap'd as a Transaction returning garbage-but-
deterministic field reads).
Field bytes inside the fixed section appear at fixed offsets named
Offset<Schema>_<Field>. The variable-length tail (packed lists,
sub-buffers, opaque payloads) lives after the fixed section. The
constructor takes a struct-of-go-values and writes them to the buffer
at compile-time-known offsets; the accessor reads from the same
offsets. No serialization step exists between buffer and value.
Lists ride as packed []byte slots inside the fixed section:
packBytesList([][]byte) → flat 4-byte-count + (4-byte-len + bytes)*unpackBytesList([]byte) [][]byte — bounds-checked, aliases buffer.packStringList([]string) / unpackStringList([]byte) []stringfor utf-8 strings.
These helpers are local to each wire/ package (no cross-VM coupling).
When one wire envelope nests another (e.g. a Block carries an
ordered list of Transaction buffers), the inner buffers are stored
in their ZAP-encoded form inside a packBytesList slot. The outer
accessor returns [][]byte; the caller round-trips each entry
through the inner schema's Wrap<Inner> to get a typed accessor.
<vm>/service.go, <vm>/api/*.go |graphvm/graphql.go |<vm>/vm.go::Initialize |luxfi/geth/core/types/* |relayvm upstream luxfi/relay/vm re-export | upstream-owned | luxfi/relay/vm/* |oraclevm upstream luxfi/oracle/vm re-export | upstream-owned | luxfi/oracle/vm/* |The chain-VM modules' RPC handlers, GraphQL schemas, and boot-time
config blobs are operator-facing and never travel on the consensus
wire. JSON is the canonical encoding for those surfaces. ZAP is for
the on-chain consensus wire only.
None.
The new final Lux network has one chain-VM wire per VM, present in
the binary from the release that ships this LP. A node presenting
non-ZAP bytes for an in-scope chain-VM surface is not a member of the
network by definition.
The pre-Quasar Edition Lux network (2020–2025) used linearcodec for
several of these surfaces (bridgevm/codec.go, thresholdvm/codec.go,
zkvm/codec.go). That network is separate and not migrated. The
codec files are deleted; an audit confirms zero luxfi/codec /
linearcodec source-file references in ~/work/lux/chains/:
grep -rn "luxfi/codec\b\|linearcodec\|codec\.Manager" \
~/work/lux/chains --include="*.go" \
| grep -v _test.go
# expected: empty
None. The binary speaks ZAP for in-scope chain-VM surfaces and JSON
for the out-of-scope RPC/config surfaces. Byte input that fails ZAP
parse on an in-scope surface is rejected. No LUXD_ENABLE_LEGACY_CODEC
knob, no dispatch by magic byte, no runtime selection. No per-VM
override flag.
None. Activation predates every block on the new final Lux network.
There are no pre-activation chain-VM blocks on this network to replay
under a different wire.
CI gates each release with:
# 1. zero luxfi/codec source-file hits in chains/
grep -rn "luxfi/codec\b\|linearcodec\|codec\.Manager\|Codec\.Marshal\|Codec\.Unmarshal" \
~/work/lux/chains --include="*.go" \
| grep -v _test.go \
|| true
# expected: empty
# 2. every <vm>/wire/ subdir builds and tests pass
cd ~/work/lux/chains && go build \
./aivm/wire/... ./bridgevm/wire/... ./dexvm/wire/... \
./evm/wire/... ./graphvm/wire/... ./identityvm/wire/... \
./keyvm/wire/... ./oraclevm/wire/... ./quantumvm/wire/... \
./relayvm/wire/... ./thresholdvm/wire/... ./zkvm/wire/...
cd ~/work/lux/chains && go test \
./aivm/wire/... ./bridgevm/wire/... ./dexvm/wire/... \
./evm/wire/... ./graphvm/wire/... ./identityvm/wire/... \
./keyvm/wire/... ./oraclevm/wire/... ./quantumvm/wire/... \
./relayvm/wire/... ./thresholdvm/wire/... ./zkvm/wire/... \
-count=1 -timeout=10m
TestBuildParseRoundTrip New<Schema>(in).Bytes() → Wrap<Schema> → field equality
TestParseRejectsNonZAP non-ZAP bytes reject at Wrap<Schema> (nil, empty, short, bad magic)
TestKindMismatch Wrap<Schema-A> on a Schema-B buffer returns ErrWrongKind
TestPeekKind PeekKind on a fresh buffer returns the constructor's Kind
TestBytesIsUnderlyingBuffer Bytes() returns the underlying ZAP buffer, no copy
Lives in ~/work/lux/chains/<vm>/wire/wire_test.go.
Chain-VM internal-wire interop with non-Go consensus stubs follows
the same rule as LP-023 and LP-184: every reference implementation
reads the schema definitions in ~/work/lux/chains/<vm>/wire/*.go
as the single source of truth, and CI gates byte-equality before any
release.
~/work/lux/chains/<vm>/wire/ — lands in the luxd release carrying
this LP.
Per-VM follow-ups that flesh out sub-type schemas (e.g. ZKProof inner
fields, FHEData inner fields, ShieldedOutput inner fields in zkvm)
are tracked under this LP as a single follow-up; the umbrella does
not block on those because the outer envelopes already carry the
inner data as opaque sub-buffers.
activates: 2025-12-25T16:20:00-08:00
activates-unix: 1766708400
Set once. Does not move. Every future Lux network-upgrade LP that
changes a chain-VM wire on the new final Lux network MUST use this
same activation marker.
CC0.