Files
zeekay 518613b5f7 The P-chain reads and writes its wire out of a schema
Every offset the C++ P-chain had was stated by hand, in four places that had
to agree: txs.hpp named the envelope and each of nineteen transactions'
fields, txs_wire.hpp named the strides, txs_wire.cpp did the arithmetic, and
block.hpp said it again for blocks. Four more files — genesis, warp, warpmsg
and components — each named their own. That is the same number written down
six times, and the fix for it is a generator.

Four schemas now say it once, and zapgen prints the readers, the builders and
the offsets from them:

    schema/wire.zap     transactions, blocks, credentials, an owner set,
                        and the two fx envelopes an output is ordered by
    schema/genesis.zap  the genesis blob
    schema/warp.zap     the message and the aggregated proof over it
    schema/warpmsg.zap  what an L1 and the P-chain say to each other

One schema per namespace, because a package name is what a backend renders as
a namespace and a file declares one.

What is left beside them — 283 lines, down from 1,463 — holds not one offset.
It holds the ARRANGEMENT, which is a fact about this chain rather than about
the format: an output's owner addresses do not live in the output, they live
in one run shared by the whole transaction, and the output names a slice of
it. That is what lets a transaction be read without walking it, and it was a
decision this chain made, so it is stated once instead of at the nineteen
places a transaction is built.

The bytes did not move, and that is checked rather than asserted. txs_test,
block_test, genesis_test and components_test compare 39 buffers against hex
the GO REFERENCE printed — a round trip would only prove this port agrees
with itself, which is what a fork also does. All 18 suites pass, and the
chain's answers to all 159 P-chain corpus vectors are byte-identical to the
answers it gave before.

Two things came out of doing it. The generator now refuses a field named as
the struct that holds it: warp's Hash message has a Hash field, and the
accessor for it is a constructor with a return type that the compiler rejects
three files from the schema that caused it — one rule in the front end, all
three backends. And zap.cmake pinned the generator to the runtime's tag, a
version that does not exist in the generator's repository, so that target had
never run; the generator has its own pin now, at zap-proto/go v1.9.0, which
reproduces every committed header byte for byte.
2026-09-06 13:20:10 -07:00

29 lines
901 B
Plaintext

# SPDX-License-Identifier: BSD-3-Clause-Eco
# The genesis blob's wire.
#
# Three lists of things that are already self-describing — allocations,
# validator transactions, chain transactions — so the container stores a u32
# LENGTH per element beside one concatenated blob rather than re-encoding
# anything. That is the same framing a block uses for its transactions, for
# the same reason.
package lux.platformvm.genesis.wire
# One allocation: the UTXO's own envelope, and the message it carried.
struct Allocation {
Utxo bytes @0
Message bytes @8
}
struct Genesis {
Timestamp u64 @0
InitialSupply u64 @8
Message text @16
UtxoLengths list<u32> @24
UtxoBlob bytes @32
ValidatorLengths list<u32> @40
ValidatorBlob bytes @48
ChainLengths list<u32> @56
ChainBlob bytes @64
}