mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The P-chain, the Z-chain and the F-chain join the Q and X chains: what each puts on a wire is stated once in chains/schema, and zapgen writes the accessors. Five schemas, five emitted modules, and one runtime under all of them — chains/rust/zap, also emitted. The five hand-written copies of that runtime are gone: 4,056 lines that were the same reader written out five times, three of them byte-identical and two variants nobody had reconciled. WHAT THE VARIANTS DISAGREED ABOUT, and it was not cosmetic. Every hand copy defaulted `Builder::new` to wire version 2; the emitted runtime keeps `new` at version 1 and names `new_v2` for the other. So the swap silently moved byte 4 of every message the P-chain wrote outside a generated builder, and the warp differential caught it against the bytes Go wrote — a one-byte divergence in a header nobody reads by hand. Every P-chain writer now says new_v2, which is what it always wrote. THE SCHEMA WAS PROVED BEFORE THE TABLE WAS DELETED. A test asserted every offset and every size in pchain.zap against the constant the chain had been using — 27 structs, every field — and it passed before a line was removed. What replaces it now compares schema to schema: every kind's envelope is Base's envelope, checked field by field against the emitted constants, so `read_envelope` reading any transaction through Base's accessors is a thing that is checked rather than a convention to remember. THE BYTES DID NOT MOVE, and every chain says so against the corpus the Go chain generated: 68 P transactions and 4 P blocks, 19 X transactions and 6 X blocks, 12 Q blocks, 22 Z blocks, 43 F transactions, each re-encoded from the values that were read and compared to Go's hex. Those are every vector the corpus expects to parse; the rest are the deliberately malformed ones, and they are still refused. `make chains` is 725 vectors, 4 implementations, 13 evaluators, AGREED on every compared field, exit 0, nothing under NOT ANSWERED. Two fields of one name and a struct named but not declared are now refused by zapgen's front end, once, for every backend. The P-chain hit the first: an envelope's owner-address POOL and the owner group a transaction hands ownership TO were both called OwnerAddrs, and the emitted Rust had the same struct member twice. They are named apart now — NewOwner on a chain ownership transfer, NetworkOwner on a network being created. WHAT IS STILL HAND-WRITTEN, and it is the P-chain only: warp.rs and warpmsg.rs (the warp envelope and its payloads), genesis.rs, persist.rs (the disk state), and components.rs (the fx primitives it spends). Those are real remaining offsets and they are next. The fx primitives belong in neither chain's schema — the X-chain makes them and the P-chain spends them, so xchain.zap and a P-chain statement of the same TransferOutput would be two statements of one format. Lifting them into a shared fx.zap needs an import across schemas that zapgen does not have; the attempt is what found the undeclared-struct hole, and the refusal now says so at the schema rather than in emitted Rust. 378 platformvm, 290 xvm, 110 quantumvm, 150 zkvm and 97 fhevm unit tests, the five corpus byte tests, the golden vectors and the differentials all pass.
91 lines
2.5 KiB
Plaintext
91 lines
2.5 KiB
Plaintext
# SPDX-License-Identifier: BSD-3-Clause-Eco
|
|
# The Z-chain wire.
|
|
#
|
|
# The offsets are the format. They are stated here once and every language's
|
|
# accessors come out of this file, so a shielded transaction built in Rust is
|
|
# the bytes a Go peer builds.
|
|
#
|
|
# A shielded chain carries most of its content as opaque runs — a commitment,
|
|
# a note, a proof — so most fields here are `bytes` and what they MEAN is the
|
|
# cryptography's business, not the wire's. What the wire owes is that a run
|
|
# ends where it says it ends.
|
|
#
|
|
# Repeated items travel as a length list beside one blob rather than as a list
|
|
# of pointers: the items are variable-width messages, and a run of lengths
|
|
# says where each begins without a pointer per item.
|
|
|
|
package zchain
|
|
|
|
type id32 = bytes_fixed[32]
|
|
|
|
# Value coming in from the transparent side, naming the output it spends.
|
|
struct TransparentIn {
|
|
TxID id32 @0
|
|
Output u32 @32
|
|
Amount u64 @36
|
|
Address bytes @44
|
|
}
|
|
|
|
# Value going out to the transparent side.
|
|
struct TransparentOut {
|
|
Amount u64 @0
|
|
Asset id32 @8
|
|
Address bytes @40
|
|
}
|
|
|
|
# Value going out to the shielded side: a commitment nobody can open without
|
|
# the note, and the note encrypted to whoever may.
|
|
struct Shielded {
|
|
Commitment bytes @0
|
|
Note bytes @8
|
|
Ephemeral bytes @16
|
|
RangeProof bytes @24
|
|
}
|
|
|
|
# A proof and the public inputs it was made over. Absence is an empty bytes
|
|
# field where a proof would be, so a proof and no proof are one field rather
|
|
# than a flag and a value that can disagree.
|
|
struct Proof {
|
|
System bytes @0
|
|
Data bytes @8
|
|
PublicLens list<u32> @16
|
|
PublicBlob bytes @24
|
|
}
|
|
|
|
# One unspent shielded output, as it is stored.
|
|
struct Utxo {
|
|
TxID id32 @0
|
|
Output u32 @32
|
|
Height u64 @36
|
|
Commitment bytes @44
|
|
Ciphertext bytes @52
|
|
Ephemeral bytes @60
|
|
}
|
|
|
|
struct Tx {
|
|
Kind u8 @0
|
|
Version u8 @1
|
|
Fee u64 @2
|
|
Expiry u64 @10
|
|
InLens list<u32> @18
|
|
InBlob bytes @26
|
|
OutLens list<u32> @34
|
|
OutBlob bytes @42
|
|
NullLens list<u32> @50
|
|
NullBlob bytes @58
|
|
ShieldedLens list<u32> @66
|
|
ShieldedBlob bytes @74
|
|
Proof bytes @82
|
|
Memo bytes @90
|
|
}
|
|
|
|
struct Block {
|
|
Parent id32 @0
|
|
Height u64 @32
|
|
Time i64 @40
|
|
TxLens list<u32> @48
|
|
TxBlob bytes @56
|
|
StateRoot bytes @64
|
|
Proof bytes @72
|
|
}
|