Files
zeekay 7cf87b230b node-unified is the node
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.
2026-09-07 16:15:25 -07:00

248 lines
8.2 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
// Native-ZAP wire plumbing shared by every X-chain tx type: the (TypeKind,
// ShapeKind) envelope dispatchers that reconstruct polymorphic fx primitives
// (value outputs/inputs, state outputs, operations, credentials) and the
// length-prefixed blob-list codec used for the variable sections. There is no
// pcodecs.Manager and no reflect: an fx primitive names itself on the wire via
// its 2-byte discriminator, and each branch calls exactly one fx-package
// Wrap*. Adding a new (fx, shape) is a new branch — no shared codec to grow.
import (
"fmt"
"github.com/luxfi/ids"
componentslux "github.com/luxfi/node/vms/components/lux"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/xvm/fxs"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/nftfx"
"github.com/luxfi/utxo/propertyfx"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/utxo/wire"
"github.com/luxfi/zap"
)
// itemLenStride is the per-element width of the u32 length list that prefixes
// every packed blob list (one length per element, elements concatenated).
const itemLenStride = 4
// childBytes returns the canonical wire envelope of a wire-serializable fx
// value (every production fx primitive exposes Bytes() []byte via its wire.go
// adapter). This is the ONE serialization contract the tx wire composes on.
func childBytes(v any) ([]byte, error) {
ws, ok := v.(interface{ Bytes() []byte })
if !ok {
return nil, fmt.Errorf("xvm txs: %T is not wire-serializable (missing Bytes() []byte)", v)
}
return ws.Bytes(), nil
}
// ---- packed blob list: (u32 length list, concatenated blob) ----
// writeBlobList packs a slice of self-contained buffers as a u32 length list
// plus a concatenated blob. AddUint32 counts elements, so the list length is
// the item count directly.
func writeBlobList(b *zap.Builder, bufs [][]byte) (lenOff, lenCount int, blob []byte) {
if len(bufs) == 0 {
return 0, 0, nil
}
lb := b.StartList(itemLenStride)
for _, buf := range bufs {
lb.AddUint32(uint32(len(buf)))
blob = append(blob, buf...)
}
lenOff, lenCount = lb.Finish()
return lenOff, lenCount, blob
}
// readBlobList slices the blob at blobPtrOff by the u32 lengths at lenPtrOff,
// returning each element's exact bytes (aliasing the parent buffer).
func readBlobList(obj zap.Object, lenPtrOff, blobPtrOff int) ([][]byte, error) {
lengths := obj.ListStride(lenPtrOff, itemLenStride)
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("xvm txs: element %d length %d overruns blob (%d)", i, size, len(blob))
}
out[i] = blob[cursor : cursor+size]
cursor += size
}
return out, nil
}
// ---- TransferableOutput / TransferableInput <-> wire ----
//
// Build side lives in baseTxWire (BaseTx composes XVMTransferOut/In directly so
// the whole tx nests in one buffer). These reconstruct the node types from the
// nested wire accessors on the read side.
// outputFromWire reconstructs a lux.TransferableOutput from a parsed wire
// TransferableOut, dispatching the inner fx Output on its own discriminator.
func outputFromWire(w wire.TransferableOut) (*lux.TransferableOutput, error) {
fxOut, err := componentslux.WrapOutputBytes(w.OutputBytes())
if err != nil {
return nil, err
}
to, ok := fxOut.(lux.TransferableOut)
if !ok {
return nil, fmt.Errorf("xvm txs: decoded output %T is not a lux.TransferableOut", fxOut)
}
return &lux.TransferableOutput{Asset: lux.Asset{ID: w.AssetID()}, Out: to}, nil
}
// inputFromWire reconstructs a lux.TransferableInput from a parsed wire
// TransferableIn, dispatching the inner fx Input on its own discriminator.
func inputFromWire(w wire.TransferableIn) (*lux.TransferableInput, error) {
fxIn, err := componentslux.WrapInputBytes(w.InputBytes())
if err != nil {
return nil, err
}
in, ok := fxIn.(lux.TransferableIn)
if !ok {
return nil, fmt.Errorf("xvm txs: decoded input %T is not a lux.TransferableIn", fxIn)
}
return &lux.TransferableInput{
UTXOID: lux.UTXOID{TxID: w.TxID(), OutputIndex: w.OutputIndex()},
Asset: lux.Asset{ID: w.AssetID()},
In: in,
}, nil
}
// ---- fx state output ([]verify.State in InitialState) ----
// wrapFxState reconstructs a fx state output (the polymorphic verify.State an
// InitialState carries) from its wire envelope. nftfx / propertyfx state
// shapes are dispatched here; every value-transfer fx family (secp256k1,
// mldsa, slhdsa, ed25519, secp256r1, schnorr, bls) is delegated to the shared
// components/lux output dispatcher so this package stays out of their lane.
func wrapFxState(env []byte) (verify.State, error) {
tk, sk, err := wire.PeekDiscriminator(env)
if err != nil {
return nil, err
}
switch tk {
case wire.TypeKindNFT:
switch sk {
case wire.ShapeKindNFTMintOutput:
return nftfx.WrapMintOutput(env)
case wire.ShapeKindNFTTransferOutput:
return nftfx.WrapTransferOutput(env)
}
case wire.TypeKindProperty:
switch sk {
case wire.ShapeKindMintOutput:
return propertyfx.WrapMintOutput(env)
case wire.ShapeKindOwnedOutput:
return propertyfx.WrapOwnedOutput(env)
}
}
return componentslux.WrapOutputBytes(env)
}
// ---- fx operation (fxs.FxOperation in Operation) ----
// wrapFxOperation reconstructs the polymorphic fx operation an Operation
// carries from its wire envelope, dispatched on (TypeKind, ShapeKind).
func wrapFxOperation(env []byte) (fxs.FxOperation, error) {
tk, sk, err := wire.PeekDiscriminator(env)
if err != nil {
return nil, err
}
switch tk {
case wire.TypeKindSecp256k1:
if sk == wire.ShapeKindMintOperation {
return secp256k1fx.WrapMintOperation(env)
}
case wire.TypeKindNFT:
switch sk {
case wire.ShapeKindNFTMintOperation:
return nftfx.WrapMintOperation(env)
case wire.ShapeKindNFTTransferOp:
return nftfx.WrapTransferOperation(env)
}
case wire.TypeKindProperty:
switch sk {
case wire.ShapeKindMintOperation:
return propertyfx.WrapMintOperation(env)
case wire.ShapeKindBurnOperation:
return propertyfx.WrapBurnOperation(env)
}
}
return nil, fmt.Errorf("xvm txs: unknown fx operation (TypeKind=0x%02x, ShapeKind=0x%02x)", tk, sk)
}
// ---- fx credential (verify.Verifiable in FxCredential) ----
// wrapFxCredential reconstructs a fx credential from its wire envelope,
// dispatched on TypeKind (every credential carries ShapeKindCredential).
func wrapFxCredential(env []byte) (verify.Verifiable, error) {
tk, sk, err := wire.PeekDiscriminator(env)
if err != nil {
return nil, err
}
if sk != wire.ShapeKindCredential {
return nil, fmt.Errorf("xvm txs: credential envelope has non-credential shape 0x%02x", sk)
}
switch tk {
case wire.TypeKindSecp256k1:
return secp256k1fx.WrapCredential(env)
case wire.TypeKindNFT:
return nftfx.WrapCredential(env)
case wire.TypeKindProperty:
return propertyfx.WrapCredential(env)
}
return nil, fmt.Errorf("xvm txs: unknown fx credential family (TypeKind=0x%02x)", tk)
}
// ---- UTXOID fixed-stride list (36 bytes: TxID 32B + OutputIndex u32) ----
const utxoIDStride = 36
// writeUTXOIDs packs []*lux.UTXOID as a fixed 36-byte-stride list. AddBytes
// counts bytes, so the caller stores len(ids) as the list count.
func writeUTXOIDs(b *zap.Builder, utxoIDs []*lux.UTXOID) (off, count int) {
if len(utxoIDs) == 0 {
return 0, 0
}
lb := b.StartList(utxoIDStride)
for _, u := range utxoIDs {
var e [utxoIDStride]byte
copy(e[0:], u.TxID[:])
e[32] = byte(u.OutputIndex)
e[33] = byte(u.OutputIndex >> 8)
e[34] = byte(u.OutputIndex >> 16)
e[35] = byte(u.OutputIndex >> 24)
lb.AddBytes(e[:])
}
off, _ = lb.Finish()
return off, len(utxoIDs)
}
// readUTXOIDs reconstructs []*lux.UTXOID from a fixed 36-byte-stride list.
func readUTXOIDs(obj zap.Object, ptrOff int) []*lux.UTXOID {
list := obj.ListStride(ptrOff, utxoIDStride)
n := list.Len()
if n == 0 {
return nil
}
out := make([]*lux.UTXOID, n)
for i := 0; i < n; i++ {
e := list.Object(i, utxoIDStride)
var txID ids.ID
copy(txID[:], e.BytesFixedSlice(0, 32))
out[i] = &lux.UTXOID{TxID: txID, OutputIndex: e.Uint32(32)}
}
return out
}