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.
248 lines
8.2 KiB
Go
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
|
|
}
|