Lux Proposals
← All proposals
LP-0023Draft

Status

The Lux P-chain wire is ZAP. The Go struct IS the wire — there is no

codec, no marshal step, no encode or decode. An AdvanceTimeTx is a

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.

The struct IS the wire


type AdvanceTimeTx struct {
    msg *zap.Message       // the wire buffer itself
    obj zap.Object         // typed window into msg
}

func (t AdvanceTimeTx) Time() uint64    { return t.obj.Uint64(OffsetTime) }  // offset read
func (t AdvanceTimeTx) Bytes() []byte   { return t.msg.Bytes() }             // return buffer

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 — there is no encoding pass over it.

This is the Cap'n-Proto / FlatBuffers invariant: **wire format is the

in-memory representation**.

tx.Bytes() returns the underlying buffer literally. Parse(b) wraps

b in a typed accessor. TxID = sha256(buffer). No re-encoding step

anywhere in the hot path.

Specification

Wire schemas

~/work/lux/node/vms/platformvm/txs/zap_native/ is the package; one

file per tx type. Each Go struct embeds a *zap.Message; the buffer

IS the value.

Tx types in scope (P-chain platformvm):


AdvanceTimeTx
RewardValidatorTx
SetL1ValidatorWeightTx
IncreaseL1ValidatorBalanceTx
DisableL1ValidatorTx
BaseTx
RegisterL1ValidatorTx
SlashValidatorTx
TransferChainOwnershipTx
RemoveChainValidatorTx
AddValidatorTx
AddDelegatorTx
CreateNetworkTx
CreateChainTx
ImportTx
ExportTx
TransformChainTx
AddPermissionlessValidatorTx
AddPermissionlessDelegatorTx
ConvertNetworkToL1Tx
CreateSovereignL1Tx
CreateAssetTx
OperationTx

Variable-length fields (Memo, Outs, Ins, Credentials, Warp messages,

Evidence) use ZAP's length-prefixed nested-object protocol — still no

codec step, still a buffer with offset reads.

Tx ID


TxID = sha256.Sum256(tx.Bytes())

tx.Bytes() returns the ZAP buffer. No re-canonicalization. No

re-encoding. The hash domain consumes whatever the wire layer

presents.

Block ID

Same shape — block bytes are the ZAP buffer for the block envelope;

block ID is sha256.Sum256(block.Bytes()).

Out of scope

Wires owned by other repositories:

External RPC surface:

Rationale

One wire. Decomplection: the wire layer does exactly one job

(present typed accessors over a byte buffer). Multiple wires would

braid two unrelated concerns (which wire to read) with one job

(read the bytes).

ZAP because the struct IS the wire. Every other option — protobuf,

RLP, linearcodec, msgpack, hand-rolled binary — is a codec: serialize

on write, deserialize on read. ZAP eliminates the step. The buffer is

the value. tx.Bytes() returns it. sha256(tx.Bytes()) requires no

re-encoding. That is the property no other option offers.

Activation at the genesis of the new final Lux network. The new

generation came online 2026-05-31 (devnet) and 2026-06-01 (testnet,

mainnet). Activation timestamp predates every block on the new

generation. Every P-chain tx on the new final Lux network is a ZAP

tx by construction. The v1.10.x luxd binary running today not yet

implementing native ZAP wire is a binary defect to close in the next

release — not a chain-history fact to preserve.

No backwards compatibility

None.

The new final Lux network has one P-chain wire, present in the binary

from the release that ships this LP. A node presenting non-ZAP bytes

for a P-chain tx is not a member of the network by definition.

The pre-Quasar Edition Lux network (2020–2025) used linearcodec for

P-chain. That network is separate and not migrated.

No replay rules

None. Activation predates every block on the network. There are no

pre-activation blocks to replay under a different wire.

No flags

None. The binary speaks ZAP. Byte input that fails ZAP parse is

rejected. No LUXD_ENABLE_LEGACY_CODEC knob, no dispatch by magic

byte, no runtime selection.

Cross-implementation byte equality

P-chain interop with non-Go consensus stubs follows the same rule as

LP-182: every reference implementation reads the schema definitions

in ~/work/lux/node/vms/platformvm/txs/zap_native/schemas.go as the

single source of truth, and CI gates byte-equality before any release.

Implementation

Reference implementation lives in

~/work/lux/node/vms/platformvm/txs/zap_native/. The legacy

~/work/lux/codec/linearcodec package is removed from the P-chain

hot path — codec.Codec{Marshal, Unmarshal} is not called from the

new final Lux generation's P-chain code.

~/work/lux/node/vms/platformvm/txs/codec.go (the legacy multi-version

Manager) is deleted alongside the linearcodec callsites. There is no

multi-version anything. There is one wire.

Test cases


TestAdvanceTimeTxBuildParseFieldEquality   build → Bytes() → Parse → fields equal
TestAdvanceTimeTxBytesIsUnderlyingBuffer   Bytes() returns msg buffer, no copy
TestAdvanceTimeTxParseRefusesNonZAP        non-ZAP bytes reject at Parse
TestPChainTxIDStability                     sha256(Bytes()) byte-identical to fixture
TestCrossImpl_ByteEquality_AllTxTypes      python/cpp/c/rust ≡ Go on every tx type
TestNoLegacyCodecInBinary                   grep build symbols — no Marshal*, no
                                            linearcodec, no codec.Codec in platformvm

Lives in ~/work/lux/node/vms/platformvm/txs/zap_native/.

Reference implementation

~/work/lux/node/vms/platformvm/txs/zap_native/ — lands in the luxd

release carrying this LP.

Cross-references

Activation marker

Every future Lux network-upgrade LP that changes the P-chain wire on

the new final Lux network MUST use the same activation marker as

this LP:


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

Set once. Does not move.

Appendix A: Benchmark results (informative)

Measured impact, Apple M1 Max, Go 1.24, real platformvm tx types

(parse only; build is one-time per-proposer and dwarfed by the L1

finality budget):

| Tx Type | Pre-LP ns/op | ZAP ns/op | Speedup | Pre-LP allocs | ZAP allocs |
|---|---|---|---|---|---|
| AdvanceTimeTx | 281.6 | 54.6 | 5.2× | 2 | 1 |
| RewardValidatorTx | 317.3 | 62.4 | 5.1× | 3 | 1 |
| SetL1ValidatorWeightTx | 423.5 | 56.2 | 7.5× | 3 | 1 |
| IncreaseL1ValidatorBalanceTx | 382.4 | 47.9 | 8.0× | 3 | 1 |
| DisableL1ValidatorTx | 453.1 | 66.8 | 6.8× | 3 | 1 |
| BaseTx | 470.0 | 60.4 | 7.8× | 3 | 1 |
| RegisterL1ValidatorTx | 1008.0 | 75.0 | 13.4× | 6 | 1 |
| SlashValidatorTx | 690.6 | 82.7 | 8.3× | 4 | 1 |
| TransferChainOwnershipTx | 2459.0 | 161.5 | 15.2× | 4 | 1 |
| RemoveChainValidatorTx | 879.6 | 117.2 | 7.5× | 4 | 1 |

Geometric mean Parse speedup across 10 types: 8.5×. Parse is the

per-validator-per-block hot path.

The "Pre-LP" column is the cost of the codec step that no longer

exists on the new final Lux network.

Reproduce:

cd ~/work/lux/node/vms/platformvm/txs/zap_native && GOWORK=off go test -bench=. -benchmem -benchtime=2s

Copyright

CC0.