mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
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.
513 lines
17 KiB
Plaintext
513 lines
17 KiB
Plaintext
# SPDX-License-Identifier: BSD-3-Clause-Eco
|
|
# The P-chain's wire, as a schema.
|
|
#
|
|
# These offsets are the format. They were stated by hand in four places —
|
|
# txs.hpp, txs_wire.hpp, txs_wire.cpp and block.hpp — and are stated here
|
|
# once instead, so the readers and the builders come out of zapgen rather
|
|
# than out of a person, and the C++ chain and the Rust chain and the Go
|
|
# reference stop being three chances to mistype the same number.
|
|
#
|
|
# Every spending transaction opens with the same eight fields — which network,
|
|
# which chain, what is spent, what is made — and then says its own thing after
|
|
# them. Each kind repeats those eight rather than naming a shared one, because
|
|
# this file is a table of consensus offsets: what a kind is, read straight
|
|
# down, with nothing to resolve first. The kind byte at 0 says which table.
|
|
#
|
|
# Fields are written in the order they are declared, which is the order of
|
|
# their offsets, which is the order of the bytes.
|
|
|
|
package lux.platformvm.wire
|
|
|
|
type id32 = bytes_fixed[32]
|
|
type addr = bytes_fixed[20]
|
|
type key = bytes_fixed[48]
|
|
type proof = bytes_fixed[96]
|
|
type sig = bytes_fixed[65]
|
|
|
|
# One output, inline in an output list. Its owners are a run in the
|
|
# transaction-wide address array, which is what AddrStart and AddrCount name:
|
|
# that is what lets a transaction be read without walking it. The record
|
|
# reserves 72 bytes where its fields end at 68 — the four that make the stride
|
|
# eight-aligned — so the width is stated rather than inferred.
|
|
struct Out @72 {
|
|
Asset id32 @0
|
|
StakeLock u64 @32
|
|
Amount u64 @40
|
|
Threshold u32 @48
|
|
OwnerLock u64 @52
|
|
AddrStart u32 @60
|
|
AddrCount u32 @64
|
|
}
|
|
|
|
# One input, inline in an input list. Its signature indices are a run in the
|
|
# transaction-wide index array. Same reserved tail as Out.
|
|
struct In @96 {
|
|
TxID id32 @0
|
|
Index u32 @32
|
|
Asset id32 @36
|
|
StakeLock u64 @68
|
|
Amount u64 @76
|
|
SigStart u32 @84
|
|
SigCount u32 @88
|
|
}
|
|
|
|
# A plain transfer: the envelope and nothing after it.
|
|
struct Base {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
}
|
|
|
|
# The one kind with no envelope: it spends nothing and names the staker whose
|
|
# time is up.
|
|
struct RewardValidator {
|
|
Kind u8 @0
|
|
StakerTxID id32 @1
|
|
}
|
|
|
|
# Bringing value in from another chain.
|
|
struct Import {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Source id32 @77
|
|
Imported list<In> @109
|
|
ImportedSigs list<u32> @117
|
|
}
|
|
|
|
# Sending value out to another chain.
|
|
struct Export {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Destination id32 @77
|
|
Exported list<Out> @109
|
|
ExportedAddrs list<addr> @117
|
|
}
|
|
|
|
# A new chain on a network this transaction is authorized over.
|
|
struct CreateChain {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Chain id32 @77
|
|
VmID id32 @109
|
|
Name text @141
|
|
FxIds list<id32> @149
|
|
Genesis bytes @157
|
|
Auth list<u32> @165
|
|
}
|
|
|
|
# Handing a chain's control to a new owner set.
|
|
struct TransferChainOwnership {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Chain id32 @77
|
|
Auth list<u32> @109
|
|
OwnerThreshold u32 @117
|
|
OwnerLocktime u64 @121
|
|
NewOwnerAddrs list<addr> @129
|
|
}
|
|
|
|
# Taking a validator off a permissioned chain.
|
|
struct RemoveChainValidator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
NodeID addr @77
|
|
Chain id32 @97
|
|
Auth list<u32> @129
|
|
}
|
|
|
|
# A validator joining the primary network, with what it stakes and where its
|
|
# reward goes. The four validator fields at 77 are the 44-byte inline record
|
|
# every staking kind opens its own section with.
|
|
struct AddValidator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
NodeID addr @77
|
|
Start u64 @97
|
|
End u64 @105
|
|
Weight u64 @113
|
|
StakeOuts list<Out> @121
|
|
StakeAddrs list<addr> @129
|
|
RewardsThreshold u32 @137
|
|
RewardsLocktime u64 @141
|
|
RewardsAddrs list<addr> @149
|
|
DelegationShares u32 @157
|
|
}
|
|
|
|
# Weight lent to someone else's validator.
|
|
struct AddDelegator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
NodeID addr @77
|
|
Start u64 @97
|
|
End u64 @105
|
|
Weight u64 @113
|
|
StakeOuts list<Out> @121
|
|
StakeAddrs list<addr> @129
|
|
RewardsThreshold u32 @137
|
|
RewardsLocktime u64 @141
|
|
RewardsAddrs list<addr> @149
|
|
}
|
|
|
|
# A validator joining a permissioned chain.
|
|
struct AddChainValidator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
NodeID addr @77
|
|
Start u64 @97
|
|
End u64 @105
|
|
Weight u64 @113
|
|
Chain id32 @121
|
|
Auth list<u32> @153
|
|
}
|
|
|
|
# A validator joining a chain that anyone may join, with the key it will be
|
|
# aggregated under and two reward owners. The signer is a 145-byte inline
|
|
# record: one byte saying whether there is one, then the key and its proof.
|
|
struct AddPermissionlessValidator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
NodeID addr @77
|
|
Start u64 @97
|
|
End u64 @105
|
|
Weight u64 @113
|
|
Chain id32 @121
|
|
SignerKind u8 @153
|
|
SignerKey key @154
|
|
SignerProof proof @202
|
|
StakeOuts list<Out> @298
|
|
StakeAddrs list<addr> @306
|
|
ValidatorRewardsThreshold u32 @314
|
|
ValidatorRewardsLocktime u64 @318
|
|
ValidatorRewardsAddrs list<addr> @326
|
|
DelegatorRewardsThreshold u32 @334
|
|
DelegatorRewardsLocktime u64 @338
|
|
DelegatorRewardsAddrs list<addr> @346
|
|
DelegationShares u32 @354
|
|
}
|
|
|
|
# Weight lent on a chain that anyone may join.
|
|
struct AddPermissionlessDelegator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
NodeID addr @77
|
|
Start u64 @97
|
|
End u64 @105
|
|
Weight u64 @113
|
|
Chain id32 @121
|
|
StakeOuts list<Out> @153
|
|
StakeAddrs list<addr> @161
|
|
RewardsThreshold u32 @169
|
|
RewardsLocktime u64 @173
|
|
RewardsAddrs list<addr> @181
|
|
}
|
|
|
|
# Paying for an L1 validator's continued registration.
|
|
struct IncreaseL1ValidatorBalance {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
ValidationID id32 @77
|
|
Balance u64 @109
|
|
}
|
|
|
|
# Standing an L1 validator down.
|
|
struct DisableL1Validator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
ValidationID id32 @77
|
|
Auth list<u32> @109
|
|
}
|
|
|
|
# A validator joining an L1, carrying the signed message the L1 said it with
|
|
# and the balance that pays for it.
|
|
struct RegisterL1Validator {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Balance u64 @77
|
|
Proof proof @85
|
|
Message bytes @181
|
|
}
|
|
|
|
# An L1 restating one of its validators' weights.
|
|
struct SetL1ValidatorWeight {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Message bytes @77
|
|
}
|
|
|
|
# Turning a permissioned chain into a staked one: the asset stake is
|
|
# denominated in and every threshold measured against it.
|
|
struct TransformChain {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Chain id32 @77
|
|
Asset id32 @109
|
|
InitialSupply u64 @141
|
|
MaximumSupply u64 @149
|
|
MinConsumptionRate u64 @157
|
|
MaxConsumptionRate u64 @165
|
|
MinValidatorStake u64 @173
|
|
MaxValidatorStake u64 @181
|
|
MinStakeDuration u32 @189
|
|
MaxStakeDuration u32 @193
|
|
MinDelegationFee u32 @197
|
|
MinDelegatorStake u64 @201
|
|
MaxValidatorWeightFactor u8 @209
|
|
UptimeRequirement u32 @210
|
|
Auth list<u32> @214
|
|
}
|
|
|
|
# One validator in a genesis set, inline at a fixed stride. A node id and two
|
|
# owner address runs do not fit a fixed slot, so each entry names a run into
|
|
# one transaction-wide blob and one transaction-wide address array — the same
|
|
# shape the envelope uses for its outputs' owners.
|
|
struct NetworkValidator {
|
|
Weight u64 @0
|
|
Balance u64 @8
|
|
SignerKey key @16
|
|
SignerProof proof @64
|
|
NodeIDStart u32 @160
|
|
NodeIDLen u32 @164
|
|
RemoveThreshold u32 @168
|
|
RemoveAddrStart u32 @172
|
|
RemoveAddrCount u32 @176
|
|
DisableThreshold u32 @180
|
|
DisableAddrStart u32 @184
|
|
DisableAddrCount u32 @188
|
|
}
|
|
|
|
# A new network, its owner set, how it admits validators, and the genesis set
|
|
# it starts with.
|
|
struct CreateNetwork {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Parent id32 @77
|
|
OwnerThreshold u32 @109
|
|
OwnerLocktime u64 @113
|
|
NewOwnerAddrs list<addr> @121
|
|
RestakeParent u8 @129
|
|
Admission u8 @130
|
|
Manager u8 @131
|
|
Threshold u64 @132
|
|
Validators list<NetworkValidator> @140
|
|
NodeIDPool bytes @148
|
|
AddrPool list<addr> @156
|
|
ManagerChainID id32 @164
|
|
ManagerAddress bytes @196
|
|
}
|
|
|
|
# A network becoming an L1: it names its manager and the validator set that
|
|
# runs it from here on.
|
|
struct ConvertNetwork {
|
|
Kind u8 @0
|
|
NetworkID u32 @1
|
|
BlockchainID id32 @5
|
|
Outs list<Out> @37
|
|
OwnerAddrs list<addr> @45
|
|
Ins list<In> @53
|
|
SigIndices list<u32> @61
|
|
Memo bytes @69
|
|
Network id32 @77
|
|
Parent id32 @109
|
|
ManagerChainID id32 @141
|
|
ManagerAddress bytes @173
|
|
Validators list<NetworkValidator> @181
|
|
NodeIDPool bytes @189
|
|
AddrPool list<addr> @197
|
|
Auth list<u32> @205
|
|
RestakeParent u8 @213
|
|
Admission u8 @214
|
|
Manager u8 @215
|
|
Threshold u64 @216
|
|
}
|
|
|
|
# ---- blocks -------------------------------------------------------------
|
|
#
|
|
# Three shapes, one prefix. A decided block stops at its timestamp, a standard
|
|
# block carries the transactions it applies, and a proposal block carries one
|
|
# transaction of its own after them. The kind byte says which was written, and
|
|
# writing the shorter one is what makes the shorter bytes.
|
|
|
|
struct Decided {
|
|
Kind u8 @0
|
|
Parent id32 @1
|
|
Height u64 @33
|
|
Time u64 @41
|
|
}
|
|
|
|
struct Standard {
|
|
Kind u8 @0
|
|
Parent id32 @1
|
|
Height u64 @33
|
|
Time u64 @41
|
|
TxLengths list<u32> @49
|
|
TxBlob bytes @57
|
|
}
|
|
|
|
struct Proposal {
|
|
Kind u8 @0
|
|
Parent id32 @1
|
|
Height u64 @33
|
|
Time u64 @41
|
|
TxLengths list<u32> @49
|
|
TxBlob bytes @57
|
|
ProposalTx bytes @65
|
|
}
|
|
|
|
# ---- what state and signatures are stored as -----------------------------
|
|
|
|
# An owner set standing alone, rather than inline in a transaction. This is
|
|
# the shape a chain owner is kept in.
|
|
struct Owner {
|
|
Threshold u32 @0
|
|
Locktime u64 @4
|
|
Addrs list<addr> @12
|
|
}
|
|
|
|
# The signatures a transaction carries, as their own message: one entry per
|
|
# credential naming a run in one shared signature array. A claimed range that
|
|
# is not inside the array it names is a refusal, not a clamp — but that is a
|
|
# judgement about meaning, and it is made by the reader, not here.
|
|
|
|
struct CredentialRun {
|
|
Start u32 @0
|
|
Count u32 @4
|
|
}
|
|
|
|
struct Credentials {
|
|
Runs list<CredentialRun> @0
|
|
Signatures list<sig> @8
|
|
}
|
|
|
|
# ---- the fx envelopes an output is ordered by ------------------------------
|
|
#
|
|
# An output's canonical bytes are its SORT KEY, so they are consensus: two
|
|
# implementations that build them differently disagree about which
|
|
# transactions are well-formed. The two-byte (TypeKind, ShapeKind) pair each
|
|
# travels behind sits OUTSIDE this message — it says which fx family and
|
|
# which shape the bytes are, which is a question about the chain rather than
|
|
# about the payload.
|
|
|
|
struct Transfer {
|
|
Amount u64 @0
|
|
Locktime u64 @8
|
|
Threshold u32 @16
|
|
Addrs list<addr> @20
|
|
}
|
|
|
|
# A transfer output held until a time, carrying the whole envelope of the
|
|
# output it releases — discriminator and all, so it is a byte run here.
|
|
struct Locked {
|
|
Locktime u64 @0
|
|
Output bytes @8
|
|
}
|
|
|
|
# One unspent output standing alone, behind its own envelope: where it came
|
|
# from, and the whole output it is — discriminator and all.
|
|
struct Utxo {
|
|
TxID id32 @0
|
|
Index u32 @32
|
|
Asset id32 @36
|
|
Output bytes @68
|
|
}
|