mirror of
https://github.com/luxfi/proto.git
synced 2026-10-30 15:21:09 +00:00
proto required luxfi/node and luxfi/node required proto, with no replace directive on either side. The cycle was not load-bearing: most of it was proto reaching into the node for packages proto already owns — p/warp, p/txs, p/signer, p/stakeable, p/security, x/txs — which now name themselves. Two were real. The UTXO components moved here, where the chains that use them already live, and the node is the only other caller, so that dependency now runs one way. And p/config held the node's chain manager to queue a chain the P-Chain had accepted a CreateChainTx for; it calls one method, so it declares one method. A P-Chain that imports the node to describe QueueChainCreation is what made two modules require each other. Dropping the requirement dropped a registration with it: lux.ParseUTXO is a registry filled by an init, which the node used to link in on proto's behalf. The packages that call it now say so themselves. Co-authored-by: Hanzo Dev <[email protected]>
220 lines
6.1 KiB
Go
220 lines
6.1 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package genesis
|
|
|
|
// Native-ZAP wire for the P-Chain genesis blob (struct-is-wire; no codec, no
|
|
// version prefix, no slot registry). The blob is one zap object carrying the
|
|
// scalar header plus three variable-length element lists — UTXOs, validator
|
|
// txs, chain txs — each encoded as a lengths list + concatenated blob (the
|
|
// same framing the migrated block package uses for its tx lists). Embedded
|
|
// txs are stored as their own self-delimiting signed bytes and re-parsed via
|
|
// txs.Parse, so their TxID (= hash(signedBytes)) is preserved byte-for-byte.
|
|
// Re-genesis means this layout is free to be the canonical one.
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
lux "github.com/luxfi/utxo"
|
|
// lux.ParseUTXO is a registry that something has to fill. The
|
|
// components/lux init is what fills it, and this package is one of the
|
|
// callers, so it says so rather than inheriting it from whoever linked it.
|
|
_ "github.com/luxfi/proto/components/lux"
|
|
"github.com/luxfi/proto/p/txs"
|
|
"github.com/luxfi/zap"
|
|
)
|
|
|
|
// Genesis object layout (size 72):
|
|
const (
|
|
genTimestamp = 0 // u64
|
|
genInitialSupply = 8 // u64
|
|
genMessage = 16 // text ptr (8B)
|
|
genUTXOLens = 24 // list ptr (8B): per-UTXO blob lengths
|
|
genUTXOBlob = 32 // bytes ptr (8B): concatenated UTXO blobs
|
|
genVdrLens = 40 // list ptr (8B)
|
|
genVdrBlob = 48 // bytes ptr (8B)
|
|
genChainLens = 56 // list ptr (8B)
|
|
genChainBlob = 64 // bytes ptr (8B)
|
|
genSize = 72
|
|
genLenStride = 4 // uint32
|
|
)
|
|
|
|
// genesis UTXO sub-object (size 16): utxo wire bytes @0, per-UTXO message @8.
|
|
const (
|
|
gutxoWire = 0
|
|
gutxoMsg = 8
|
|
gutxoSize = 16
|
|
)
|
|
|
|
func marshalGenesis(g *Genesis) ([]byte, error) {
|
|
utxoBlobs := make([][]byte, len(g.UTXOs))
|
|
for i, u := range g.UTXOs {
|
|
raw, err := marshalGenesisUTXO(u)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("genesis: marshal utxo %d: %w", i, err)
|
|
}
|
|
utxoBlobs[i] = raw
|
|
}
|
|
vdrBlobs, err := txBlobs(g.Validators)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("genesis: marshal validators: %w", err)
|
|
}
|
|
chainBlobs, err := txBlobs(g.Chains)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("genesis: marshal chains: %w", err)
|
|
}
|
|
|
|
b := zap.NewBuilder(zap.HeaderSize + genSize + 1024)
|
|
utxoLenOff, utxoLenCount, utxoBlob := writeBlobList(b, utxoBlobs)
|
|
vdrLenOff, vdrLenCount, vdrBlob := writeBlobList(b, vdrBlobs)
|
|
chainLenOff, chainLenCount, chainBlob := writeBlobList(b, chainBlobs)
|
|
|
|
ob := b.StartObject(genSize)
|
|
ob.SetUint64(genTimestamp, g.Timestamp)
|
|
ob.SetUint64(genInitialSupply, g.InitialSupply)
|
|
ob.SetText(genMessage, g.Message)
|
|
ob.SetList(genUTXOLens, utxoLenOff, utxoLenCount)
|
|
ob.SetBytes(genUTXOBlob, utxoBlob)
|
|
ob.SetList(genVdrLens, vdrLenOff, vdrLenCount)
|
|
ob.SetBytes(genVdrBlob, vdrBlob)
|
|
ob.SetList(genChainLens, chainLenOff, chainLenCount)
|
|
ob.SetBytes(genChainBlob, chainBlob)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func parseGenesis(genesisBytes []byte) (*Genesis, error) {
|
|
msg, err := zap.Parse(genesisBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj := msg.Root()
|
|
|
|
g := &Genesis{
|
|
Timestamp: obj.Uint64(genTimestamp),
|
|
InitialSupply: obj.Uint64(genInitialSupply),
|
|
Message: obj.Text(genMessage),
|
|
}
|
|
|
|
utxoBlobs, err := readBlobList(obj, genUTXOLens, genUTXOBlob)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("genesis: read utxos: %w", err)
|
|
}
|
|
if len(utxoBlobs) > 0 {
|
|
g.UTXOs = make([]*UTXO, len(utxoBlobs))
|
|
for i, raw := range utxoBlobs {
|
|
u, err := parseGenesisUTXO(raw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("genesis: parse utxo %d: %w", i, err)
|
|
}
|
|
g.UTXOs[i] = u
|
|
}
|
|
}
|
|
|
|
if g.Validators, err = parseTxBlobs(obj, genVdrLens, genVdrBlob); err != nil {
|
|
return nil, fmt.Errorf("genesis: read validators: %w", err)
|
|
}
|
|
if g.Chains, err = parseTxBlobs(obj, genChainLens, genChainBlob); err != nil {
|
|
return nil, fmt.Errorf("genesis: read chains: %w", err)
|
|
}
|
|
return g, nil
|
|
}
|
|
|
|
func marshalGenesisUTXO(u *UTXO) ([]byte, error) {
|
|
wire, err := u.UTXO.WireBytes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b := zap.NewBuilder(zap.HeaderSize + gutxoSize + len(wire) + len(u.Message))
|
|
ob := b.StartObject(gutxoSize)
|
|
ob.SetBytes(gutxoWire, wire)
|
|
ob.SetBytes(gutxoMsg, u.Message)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func parseGenesisUTXO(blob []byte) (*UTXO, error) {
|
|
msg, err := zap.Parse(blob)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj := msg.Root()
|
|
inner, err := lux.ParseUTXO(obj.Bytes(gutxoWire))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var message []byte
|
|
if m := obj.Bytes(gutxoMsg); len(m) > 0 {
|
|
message = append([]byte(nil), m...)
|
|
}
|
|
return &UTXO{UTXO: *inner, Message: message}, nil
|
|
}
|
|
|
|
// ---- shared lengths-list + blob framing ----
|
|
|
|
func txBlobs(list []*txs.Tx) ([][]byte, error) {
|
|
if len(list) == 0 {
|
|
return nil, nil
|
|
}
|
|
out := make([][]byte, len(list))
|
|
for i, tx := range list {
|
|
if tx == nil {
|
|
return nil, fmt.Errorf("nil tx at index %d", i)
|
|
}
|
|
out[i] = tx.Bytes()
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func writeBlobList(b *zap.Builder, blobs [][]byte) (lenOff, lenCount int, blob []byte) {
|
|
if len(blobs) == 0 {
|
|
return 0, 0, nil
|
|
}
|
|
lb := b.StartList(genLenStride)
|
|
for _, raw := range blobs {
|
|
lb.AddUint32(uint32(len(raw)))
|
|
blob = append(blob, raw...)
|
|
}
|
|
lenOff, lenCount = lb.Finish()
|
|
return lenOff, lenCount, blob
|
|
}
|
|
|
|
func readBlobList(obj zap.Object, lenPtrOff, blobPtrOff int) ([][]byte, error) {
|
|
lengths := obj.ListStride(lenPtrOff, genLenStride)
|
|
n := lengths.Len()
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
blob := obj.Bytes(blobPtrOff)
|
|
out := make([][]byte, n)
|
|
cursor := 0
|
|
for i := 0; i < n; i++ {
|
|
size := int(lengths.Uint32(i))
|
|
if size < 0 || cursor+size > len(blob) {
|
|
return nil, fmt.Errorf("element %d length %d overruns blob (%d)", i, size, len(blob))
|
|
}
|
|
out[i] = blob[cursor : cursor+size]
|
|
cursor += size
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func parseTxBlobs(obj zap.Object, lenPtrOff, blobPtrOff int) ([]*txs.Tx, error) {
|
|
blobs, err := readBlobList(obj, lenPtrOff, blobPtrOff)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(blobs) == 0 {
|
|
return nil, nil
|
|
}
|
|
out := make([]*txs.Tx, len(blobs))
|
|
for i, raw := range blobs {
|
|
tx, err := txs.Parse(raw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse tx %d: %w", i, err)
|
|
}
|
|
out[i] = tx
|
|
}
|
|
return out, nil
|
|
}
|