mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
422 lines
12 KiB
Go
422 lines
12 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package lux
|
|
|
|
// Native ZAP wire for the shared UTXO value tree: the struct IS the wire.
|
|
// Each type Marshal()s to a single zap object (StartObject / Set* /
|
|
// FinishAsRoot) and Unmarshal()s via offset accessors — no codec, no
|
|
// pcodecs.Manager, no serialize-tag reflection, no codec-version prefix.
|
|
//
|
|
// The polymorphic inner Out/In (fx feature-extension outputs/inputs) is
|
|
// composed, not re-encoded here: each fx primitive already owns a native
|
|
// ZAP wire envelope via its Bytes() method (and the WrapOutputBytes /
|
|
// wrapInputBytes fx-aware dispatchers reconstruct it). components/lux
|
|
// stays fx-agnostic — it hand-rolls only the outer node envelope and
|
|
// stores the fx child as an opaque bytes field. This is the same
|
|
// (TypeKind, ShapeKind, ZAP-message) envelope the fx packages hit on the
|
|
// P/X data path, so components/lux wire bytes stay consistent with the
|
|
// canonical luxfi/utxo tree without collapsing the two type trees (#58).
|
|
//
|
|
// Object fixed sections (all offsets object-relative, little-endian):
|
|
//
|
|
// Asset assetID 32B @0 size 32
|
|
// UTXOID txID 32B @0, index u32 @32 size 36
|
|
// TransferableOutput assetID 32B @0, outBytes ptr @32 size 40
|
|
// TransferableInput txID 32B @0, index u32 @32, assetID 32B @36, inBytes ptr @68 size 76
|
|
// UTXO txID 32B @0, index u32 @32, assetID 32B @36, outBytes ptr @68 size 76
|
|
// BaseTx networkID u32 @0, blockchainID 32B @4, outsLen ptr @36,
|
|
// outsBlob ptr @44, insLen ptr @52, insBlob ptr @60, memo ptr @68 size 76
|
|
// Metadata unsignedBytes ptr @0, signedBytes ptr @8 size 16
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
"github.com/luxfi/zap"
|
|
)
|
|
|
|
// ErrOutNotWireSerializable is returned when an in-memory Out/In is not a
|
|
// known fx primitive carrying a native ZAP Bytes() adapter.
|
|
var ErrOutNotWireSerializable = errors.New("lux: fx output/input type does not implement wire-serializable Bytes() []byte")
|
|
|
|
// wireSerializable is the minimal contract every fx primitive's wire.go
|
|
// adapter satisfies for the polymorphic child payload.
|
|
type wireSerializable interface{ Bytes() []byte }
|
|
|
|
func childBytes(v any) ([]byte, error) {
|
|
ws, ok := v.(wireSerializable)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %T", ErrOutNotWireSerializable, v)
|
|
}
|
|
return ws.Bytes(), nil
|
|
}
|
|
|
|
// ---- fixed offsets / sizes ----
|
|
|
|
const (
|
|
offAssetOnly = 0
|
|
sizeAsset = 32
|
|
|
|
offUTXOIDTxID = 0
|
|
offUTXOIDIndex = 32
|
|
sizeUTXOID = 36
|
|
|
|
offTOAsset = 0
|
|
offTOOut = 32
|
|
sizeTO = 40
|
|
|
|
offTITxID = 0
|
|
offTIIndex = 32
|
|
offTIAsset = 36
|
|
offTIIn = 68
|
|
sizeTI = 76
|
|
|
|
offUTXOTxID = 0
|
|
offUTXOIndex = 32
|
|
offUTXOAsset = 36
|
|
offUTXOOut = 68
|
|
sizeUTXOObj = 76
|
|
|
|
offBTNetworkID = 0
|
|
offBTBlockchainID = 4
|
|
offBTOutsLen = 36
|
|
offBTOutsBlob = 44
|
|
offBTInsLen = 52
|
|
offBTInsBlob = 60
|
|
offBTMemo = 68
|
|
sizeBT = 76
|
|
|
|
offMDUnsigned = 0
|
|
offMDSigned = 8
|
|
sizeMD = 16
|
|
|
|
idLen = 32
|
|
itemLenStrid = 4 // uint32 element for the per-item length lists
|
|
)
|
|
|
|
// ---- Asset ----
|
|
|
|
func (a *Asset) Marshal() ([]byte, error) {
|
|
b := zap.NewBuilder(zap.HeaderSize + sizeAsset)
|
|
ob := b.StartObject(sizeAsset)
|
|
ob.SetBytesFixed(offAssetOnly, a.ID[:])
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func (a *Asset) Unmarshal(bytes []byte) error {
|
|
msg, err := zap.Parse(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(a.ID[:], msg.Root().BytesFixedSlice(offAssetOnly, idLen))
|
|
return nil
|
|
}
|
|
|
|
// ---- UTXOID ----
|
|
|
|
func (u *UTXOID) Marshal() ([]byte, error) {
|
|
b := zap.NewBuilder(zap.HeaderSize + sizeUTXOID)
|
|
ob := b.StartObject(sizeUTXOID)
|
|
ob.SetBytesFixed(offUTXOIDTxID, u.TxID[:])
|
|
ob.SetUint32(offUTXOIDIndex, u.OutputIndex)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func (u *UTXOID) Unmarshal(bytes []byte) error {
|
|
msg, err := zap.Parse(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj := msg.Root()
|
|
copy(u.TxID[:], obj.BytesFixedSlice(offUTXOIDTxID, idLen))
|
|
u.OutputIndex = obj.Uint32(offUTXOIDIndex)
|
|
return nil
|
|
}
|
|
|
|
// ---- TransferableOutput ----
|
|
|
|
func (out *TransferableOutput) Marshal() ([]byte, error) {
|
|
if out == nil || out.Out == nil {
|
|
return nil, ErrNilTransferableFxOutput
|
|
}
|
|
child, err := childBytes(out.Out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b := zap.NewBuilder(zap.HeaderSize + sizeTO + len(child) + 32)
|
|
ob := b.StartObject(sizeTO)
|
|
ob.SetBytesFixed(offTOAsset, out.Asset.ID[:])
|
|
ob.SetBytes(offTOOut, child)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func (out *TransferableOutput) Unmarshal(bytes []byte) error {
|
|
msg, err := zap.Parse(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj := msg.Root()
|
|
copy(out.Asset.ID[:], obj.BytesFixedSlice(offTOAsset, idLen))
|
|
fxOut, err := WrapOutputBytes(obj.Bytes(offTOOut))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
to, ok := fxOut.(TransferableOut)
|
|
if !ok {
|
|
return fmt.Errorf("lux: decoded output %T is not a TransferableOut", fxOut)
|
|
}
|
|
out.Out = to
|
|
return nil
|
|
}
|
|
|
|
// ---- TransferableInput ----
|
|
|
|
func (in *TransferableInput) Marshal() ([]byte, error) {
|
|
if in == nil || in.In == nil {
|
|
return nil, ErrNilTransferableFxInput
|
|
}
|
|
child, err := childBytes(in.In)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b := zap.NewBuilder(zap.HeaderSize + sizeTI + len(child) + 32)
|
|
ob := b.StartObject(sizeTI)
|
|
ob.SetBytesFixed(offTITxID, in.UTXOID.TxID[:])
|
|
ob.SetUint32(offTIIndex, in.UTXOID.OutputIndex)
|
|
ob.SetBytesFixed(offTIAsset, in.Asset.ID[:])
|
|
ob.SetBytes(offTIIn, child)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func (in *TransferableInput) Unmarshal(bytes []byte) error {
|
|
msg, err := zap.Parse(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj := msg.Root()
|
|
copy(in.UTXOID.TxID[:], obj.BytesFixedSlice(offTITxID, idLen))
|
|
in.UTXOID.OutputIndex = obj.Uint32(offTIIndex)
|
|
copy(in.Asset.ID[:], obj.BytesFixedSlice(offTIAsset, idLen))
|
|
fxIn, err := wrapInputBytes(obj.Bytes(offTIIn))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
in.In = fxIn
|
|
return nil
|
|
}
|
|
|
|
// ---- UTXO ----
|
|
|
|
func (utxo *UTXO) Marshal() ([]byte, error) {
|
|
if utxo == nil || utxo.Out == nil {
|
|
return nil, errEmptyUTXO
|
|
}
|
|
child, err := childBytes(utxo.Out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b := zap.NewBuilder(zap.HeaderSize + sizeUTXOObj + len(child) + 32)
|
|
ob := b.StartObject(sizeUTXOObj)
|
|
ob.SetBytesFixed(offUTXOTxID, utxo.UTXOID.TxID[:])
|
|
ob.SetUint32(offUTXOIndex, utxo.UTXOID.OutputIndex)
|
|
ob.SetBytesFixed(offUTXOAsset, utxo.Asset.ID[:])
|
|
ob.SetBytes(offUTXOOut, child)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func (utxo *UTXO) Unmarshal(bytes []byte) error {
|
|
msg, err := zap.Parse(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj := msg.Root()
|
|
copy(utxo.UTXOID.TxID[:], obj.BytesFixedSlice(offUTXOTxID, idLen))
|
|
utxo.UTXOID.OutputIndex = obj.Uint32(offUTXOIndex)
|
|
copy(utxo.Asset.ID[:], obj.BytesFixedSlice(offUTXOAsset, idLen))
|
|
fxOut, err := WrapOutputBytes(obj.Bytes(offUTXOOut))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
utxo.Out = fxOut
|
|
return nil
|
|
}
|
|
|
|
// ---- BaseTx ----
|
|
|
|
func (t *BaseTx) Marshal() ([]byte, error) {
|
|
b := zap.NewBuilder(zap.HeaderSize + sizeBT + 256)
|
|
|
|
outsLenOff, outsLenCount, outsBlob, err := writeOutList(b, t.Outs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
insLenOff, insLenCount, insBlob, err := writeInList(b, t.Ins)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ob := b.StartObject(sizeBT)
|
|
ob.SetUint32(offBTNetworkID, t.NetworkID)
|
|
ob.SetBytesFixed(offBTBlockchainID, t.BlockchainID[:])
|
|
ob.SetList(offBTOutsLen, outsLenOff, outsLenCount)
|
|
ob.SetBytes(offBTOutsBlob, outsBlob)
|
|
ob.SetList(offBTInsLen, insLenOff, insLenCount)
|
|
ob.SetBytes(offBTInsBlob, insBlob)
|
|
ob.SetBytes(offBTMemo, t.Memo)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func (t *BaseTx) Unmarshal(bytes []byte) error {
|
|
msg, err := zap.Parse(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj := msg.Root()
|
|
t.NetworkID = obj.Uint32(offBTNetworkID)
|
|
copy(t.BlockchainID[:], obj.BytesFixedSlice(offBTBlockchainID, idLen))
|
|
if t.Outs, err = readOutList(obj, offBTOutsLen, offBTOutsBlob); err != nil {
|
|
return err
|
|
}
|
|
if t.Ins, err = readInList(obj, offBTInsLen, offBTInsBlob); err != nil {
|
|
return err
|
|
}
|
|
if m := obj.Bytes(offBTMemo); len(m) > 0 {
|
|
t.Memo = append([]byte(nil), m...)
|
|
} else {
|
|
t.Memo = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeOutList marshals each output and packs (per-item u32 length list,
|
|
// concatenated blob). AddUint32 counts elements, so the list length is the
|
|
// item count directly (unlike an AddBytes list, which counts bytes).
|
|
func writeOutList(b *zap.Builder, outs []*TransferableOutput) (lenOff, lenCount int, blob []byte, err error) {
|
|
if len(outs) == 0 {
|
|
return 0, 0, nil, nil
|
|
}
|
|
lb := b.StartList(itemLenStrid)
|
|
for i, o := range outs {
|
|
raw, err := o.Marshal()
|
|
if err != nil {
|
|
return 0, 0, nil, fmt.Errorf("output %d: %w", i, err)
|
|
}
|
|
lb.AddUint32(uint32(len(raw)))
|
|
blob = append(blob, raw...)
|
|
}
|
|
lenOff, lenCount = lb.Finish()
|
|
return lenOff, lenCount, blob, nil
|
|
}
|
|
|
|
func writeInList(b *zap.Builder, ins []*TransferableInput) (lenOff, lenCount int, blob []byte, err error) {
|
|
if len(ins) == 0 {
|
|
return 0, 0, nil, nil
|
|
}
|
|
lb := b.StartList(itemLenStrid)
|
|
for i, in := range ins {
|
|
raw, err := in.Marshal()
|
|
if err != nil {
|
|
return 0, 0, nil, fmt.Errorf("input %d: %w", i, err)
|
|
}
|
|
lb.AddUint32(uint32(len(raw)))
|
|
blob = append(blob, raw...)
|
|
}
|
|
lenOff, lenCount = lb.Finish()
|
|
return lenOff, lenCount, blob, nil
|
|
}
|
|
|
|
func readOutList(obj zap.Object, lenPtrOff, blobPtrOff int) ([]*TransferableOutput, error) {
|
|
lengths := obj.ListStride(lenPtrOff, itemLenStrid)
|
|
n := lengths.Len()
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
blob := obj.Bytes(blobPtrOff)
|
|
out := make([]*TransferableOutput, 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("lux: output %d length %d overruns blob (%d)", i, size, len(blob))
|
|
}
|
|
o := &TransferableOutput{}
|
|
if err := o.Unmarshal(blob[cursor : cursor+size]); err != nil {
|
|
return nil, fmt.Errorf("lux: unmarshal output %d: %w", i, err)
|
|
}
|
|
out[i] = o
|
|
cursor += size
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func readInList(obj zap.Object, lenPtrOff, blobPtrOff int) ([]*TransferableInput, error) {
|
|
lengths := obj.ListStride(lenPtrOff, itemLenStrid)
|
|
n := lengths.Len()
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
blob := obj.Bytes(blobPtrOff)
|
|
ins := make([]*TransferableInput, 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("lux: input %d length %d overruns blob (%d)", i, size, len(blob))
|
|
}
|
|
in := &TransferableInput{}
|
|
if err := in.Unmarshal(blob[cursor : cursor+size]); err != nil {
|
|
return nil, fmt.Errorf("lux: unmarshal input %d: %w", i, err)
|
|
}
|
|
ins[i] = in
|
|
cursor += size
|
|
}
|
|
return ins, nil
|
|
}
|
|
|
|
// ---- Metadata ----
|
|
|
|
func (md *Metadata) Marshal() ([]byte, error) {
|
|
b := zap.NewBuilder(zap.HeaderSize + sizeMD + len(md.unsignedBytes) + len(md.bytes) + 32)
|
|
ob := b.StartObject(sizeMD)
|
|
ob.SetBytes(offMDUnsigned, md.unsignedBytes)
|
|
ob.SetBytes(offMDSigned, md.bytes)
|
|
ob.FinishAsRoot()
|
|
return b.Finish(), nil
|
|
}
|
|
|
|
func (md *Metadata) Unmarshal(bytes []byte) error {
|
|
msg, err := zap.Parse(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj := msg.Root()
|
|
unsigned := append([]byte(nil), obj.Bytes(offMDUnsigned)...)
|
|
signed := append([]byte(nil), obj.Bytes(offMDSigned)...)
|
|
md.Initialize(unsigned, signed)
|
|
return nil
|
|
}
|
|
|
|
// ---- OutputOwners path ----
|
|
|
|
// MarshalOwner is the ONE canonical byte encoding of an owner: the fx's
|
|
// own native ZAP OutputOwners envelope (TypeKindReserved,
|
|
// ShapeKindOutputOwners). Takes `any` to match the fx.Owned.Owners()
|
|
// surface. Same wire the fx TransferOutput embeds — no second encoding.
|
|
func MarshalOwner(o any) ([]byte, error) {
|
|
return childBytes(o)
|
|
}
|
|
|
|
// UnmarshalOwner is the exact inverse of MarshalOwner: parses the
|
|
// canonical OutputOwners envelope back into *secp256k1fx.OutputOwners.
|
|
func UnmarshalOwner(b []byte) (*secp256k1fx.OutputOwners, error) {
|
|
return secp256k1fx.WrapOutputOwners(b)
|
|
}
|