wallet/x + components/lux: complete xvm native-wire consumer

Tail of the xvm codec kill (ddb3fbca93): the X-chain wallet builder + signer
now rebuild signed wire bytes as unsigned ‖ fx credential envelopes over the
native luxfi/utxo/wire form, and components/lux parses fx Inputs from their wire
envelope by concrete type. No linearcodec, no reflection on the wallet path.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-12 16:54:12 -07:00
co-authored by Hanzo Dev
parent ddb3fbca93
commit 6b25706366
7 changed files with 23 additions and 301 deletions
+9
View File
@@ -177,6 +177,15 @@ func wrapOutput(b []byte, tk wire.TypeKind, sk wire.ShapeKind) (verify.State, er
return nil, fmt.Errorf("zap utxo dispatch: unknown (TypeKind=0x%02x, ShapeKind=0x%02x)", tk, sk)
}
// WrapInputBytes is the public entry to the fx-aware input dispatcher — the
// input-side counterpart of WrapOutputBytes. It reconstructs the polymorphic
// fx Input from its wire envelope; the concrete type (e.g.
// *secp256k1fx.TransferInput) also satisfies luxfi/utxo's TransferableIn, so
// consumers holding the utxo type tree (xvm/txs) can type-assert across.
func WrapInputBytes(b []byte) (TransferableIn, error) {
return wrapInputBytes(b)
}
// wrapInputBytes is the input-side counterpart of wrapOutput: it
// reconstructs a TransferableIn from its fx wire envelope, dispatching on
// the (TypeKind, ShapeKind) discriminator. Each branch calls exactly one
-165
View File
@@ -1,165 +0,0 @@
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package pcodecs is the canonical construction site for every
// codec.Manager + codec instance under node/vms. Wave 2G-Internal of
// the codec rip (#101) replaces the previous direct imports of
// github.com/luxfi/codec / linearcodec / zapcodec with a single
// import of github.com/luxfi/proto/zap_codec — the proto-layer wire
// codec entry point established in Wave 2G-Wallet.
//
// Layout:
//
// - Type aliases re-export the proto/zap_codec surfaces every VM
// consumer needs (Manager, Registry, LinearCodec, ZAPCodec, Errs,
// Packer, VersionSize, sentinel errors).
// - Constructor helpers return fresh Manager / Codec instances at
// canonical size budgets (Default, MaxInt32, MaxInt, Sized). They
// are wire-agnostic — each VM's own codec.go layer registers its
// type set on top.
//
// pcodecs has NO knowledge of any specific VM's type set. VM-specific
// registration stays in the VM's own codec.go (e.g.
// vms/platformvm/txs/codec.go). This split keeps pcodecs a leaf package
// — every node/vms subpackage is free to import it without closing an
// import cycle.
//
// Wire format: ZAP-native (little-endian) — the underlying impl is
// luxfi/codec/zapcodec routed through proto/zap_codec. The "Linear"
// name on LinearCodec / NewLinearCodec refers to LINEAR TYPE-ID
// ASSIGNMENT (sequential ids assigned in registration order), NOT to
// the historical linearcodec big-endian wire format. Activation
// per LP-023 (proto/zap_native/codec_select.go: ZAPActivationUnix=0).
package pcodecs
import (
"math"
"github.com/luxfi/proto/zap_codec"
)
// Manager is the multi-version wire codec surface every node/vms
// consumer holds. Aliased to zap_codec.MultiManager so the rest of
// node/vms references pcodecs.Manager rather than reaching into
// proto/zap_codec or luxfi/codec directly.
type Manager = zap_codec.MultiManager
// Registry is the type-registration surface. Aliased to
// zap_codec.Registry — structurally identical to the legacy
// codec.Registry interface (RegisterType only).
type Registry = zap_codec.Registry
// LinearCodec is the union of Registry + Codec + SkipRegistrations.
// Used by VMs that need SkipRegistrations on their per-version codec
// to preserve historical type-id slot layouts (Apricot / Banff /
// Durango / Quasar registration sequences).
//
// Despite the name, this codec is backed by luxfi/codec/zapcodec — the
// "Linear" refers to LINEAR TYPE-ID ASSIGNMENT, not to the legacy
// linearcodec big-endian wire layout.
type LinearCodec = zap_codec.LinearCodec
// ZAPCodec is an explicit alias for the same zapcodec-backed Codec
// surface — used by post-cutover wire layouts (currently platformvm
// txs V2) that want to emphasise the ZAP-native wire layout. Same
// backing type as LinearCodec; the alias exists for readability.
type ZAPCodec = zap_codec.ZAPCodec
// Errs is the multi-error accumulator used by per-VM codec.go files
// that fan in many RegisterCodec / RegisterType calls under a single
// error tap.
type Errs = zap_codec.Errs
// Packer is the wire-byte packer used by VM tests that drive
// MarshalInto / UnmarshalFrom byte streams directly.
type Packer = zap_codec.Packer
// VersionSize is the on-wire length of the codec-version prefix
// (2 bytes). VM fee-complexity calculations subtract this from
// observed byte sizes to isolate the payload component.
const VersionSize = zap_codec.VersionSize
// Sentinel errors re-exported from proto/zap_codec so VM packages can
// assert on them without importing proto/zap_codec themselves.
var (
ErrCantPackVersion = zap_codec.ErrCantPackVersion
ErrCantUnpackVersion = zap_codec.ErrCantUnpackVersion
ErrUnknownVersion = zap_codec.ErrUnknownVersion
ErrMaxSliceLenExceeded = zap_codec.ErrMaxSliceLenExceeded
ErrMaxSizeExceeded = zap_codec.ErrMaxSizeExceeded
ErrMarshalNil = zap_codec.ErrMarshalNil
ErrUnmarshalNil = zap_codec.ErrUnmarshalNil
ErrDoesNotImplementInterface = zap_codec.ErrDoesNotImplementInterface
ErrExtraSpace = zap_codec.ErrExtraSpace
ErrInsufficientLength = zap_codec.ErrInsufficientLength
)
// LongLen is the on-wire length of a uint64 (8 bytes). Used by index
// code to size cursor buffers.
const LongLen = zap_codec.LongLen
// IntLen is the on-wire length of a uint32 (4 bytes). Used by p2p
// response-size accounting code.
const IntLen = zap_codec.IntLen
// ShortLen is the on-wire length of a uint16 (2 bytes).
const ShortLen = zap_codec.ShortLen
// ByteLen is the on-wire length of a uint8 (1 byte).
const ByteLen = zap_codec.ByteLen
// BoolLen is the on-wire length of a bool (1 byte).
const BoolLen = zap_codec.BoolLen
// NewLinearCodec returns a fresh ZAP-native Codec instance with the
// default ("serialize") struct tag. The "Linear" name refers to
// LINEAR TYPE-ID ASSIGNMENT (sequential ids assigned in registration
// order), NOT to the legacy linearcodec big-endian wire format.
func NewLinearCodec() LinearCodec {
return zap_codec.NewLinearCodec()
}
// NewLinearCodecWithTags returns a fresh ZAP-native Codec instance
// that honours the supplied struct-tag names. Used by the metadata
// codec wiring where v0:"true" / v1:"true" tags select per-version
// field sets.
func NewLinearCodecWithTags(tags ...string) LinearCodec {
return zap_codec.NewLinearCodecWithTags(tags...)
}
// NewZAPCodec returns a fresh ZAP-native Codec instance. Alias for
// NewLinearCodec — both return the same zapcodec-backed Codec; the
// name exists for call sites that want to emphasise the ZAP-native
// wire layout (e.g. platformvm txs V2).
func NewZAPCodec() ZAPCodec {
return zap_codec.NewZAPCodec()
}
// NewDefaultManager returns a fresh multi-version Manager with the
// default 1 MiB wire-payload size.
func NewDefaultManager() Manager {
return zap_codec.NewDefaultManager()
}
// NewManager returns a fresh multi-version Manager with the supplied
// max wire-payload size.
func NewManager(maxSize uint64) Manager {
return zap_codec.NewManager(maxSize)
}
// NewMaxInt32Manager returns a fresh multi-version Manager sized for
// genesis-style blobs (math.MaxInt32 budget). VM genesis codec wiring
// reaches for this rather than re-deriving the budget at every call
// site.
func NewMaxInt32Manager() Manager {
return zap_codec.NewMaxInt32Manager()
}
// NewMaxIntManager returns a fresh multi-version Manager sized for
// warp / proposervm-block style payloads (math.MaxInt budget —
// effectively unbounded). The p2p layer caps real-world wire sizes
// well below this.
func NewMaxIntManager() Manager {
return zap_codec.NewManager(uint64(math.MaxInt))
}
-113
View File
@@ -1,113 +0,0 @@
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package pcodecsmock provides a gomock-driven mock of the
// pcodecs.Manager interface (= proto/zap_codec.MultiManager) so
// node/vms test files can program codec expectations without
// importing proto/zap_codec or luxfi/codec directly.
//
// Wave 2G-Internal of the codec rip (#101) replaces the previous
// re-export of luxfi/codec/codecmock with an in-tree mock of the
// proto/zap_codec.MultiManager interface. The mock satisfies
// pcodecs.Manager by shape — RegisterCodec / Marshal / Unmarshal /
// Size — and is independent of the underlying codec wire format.
package pcodecsmock
import (
"reflect"
"github.com/luxfi/proto/zap_codec"
gomock "go.uber.org/mock/gomock"
)
// Compile-time check: Manager satisfies zap_codec.MultiManager (the
// interface pcodecs.Manager aliases). Keeps the mock in sync with the
// underlying interface — any new method on MultiManager fails this
// assertion until the mock is updated.
var _ zap_codec.MultiManager = (*Manager)(nil)
// Manager is the gomock-driven mock of pcodecs.Manager (=
// zap_codec.MultiManager). Tests reach for pcodecsmock.NewManager(ctrl)
// to obtain a mock that satisfies pcodecs.Manager by shape.
type Manager struct {
ctrl *gomock.Controller
recorder *ManagerRecorder
}
// ManagerRecorder is the gomock recorder for Manager. Tests program
// expectations via mock.EXPECT().<Method>(...).Return(...).
type ManagerRecorder struct {
mock *Manager
}
// NewManager constructs a fresh Manager mock against the supplied
// gomock controller.
func NewManager(ctrl *gomock.Controller) *Manager {
m := &Manager{ctrl: ctrl}
m.recorder = &ManagerRecorder{mock: m}
return m
}
// EXPECT returns the recorder so tests can program expectations.
func (m *Manager) EXPECT() *ManagerRecorder {
return m.recorder
}
// RegisterCodec mocks the corresponding MultiManager method.
func (m *Manager) RegisterCodec(version uint16, codec zap_codec.Codec) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RegisterCodec", version, codec)
ret0, _ := ret[0].(error)
return ret0
}
// RegisterCodec expectation.
func (mr *ManagerRecorder) RegisterCodec(version, codec any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterCodec", reflect.TypeOf((*Manager)(nil).RegisterCodec), version, codec)
}
// Marshal mocks the corresponding MultiManager method.
func (m *Manager) Marshal(version uint16, source interface{}) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Marshal", version, source)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Marshal expectation.
func (mr *ManagerRecorder) Marshal(version, source any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*Manager)(nil).Marshal), version, source)
}
// Unmarshal mocks the corresponding MultiManager method.
func (m *Manager) Unmarshal(source []byte, destination interface{}) (uint16, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Unmarshal", source, destination)
ret0, _ := ret[0].(uint16)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Unmarshal expectation.
func (mr *ManagerRecorder) Unmarshal(source, destination any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unmarshal", reflect.TypeOf((*Manager)(nil).Unmarshal), source, destination)
}
// Size mocks the corresponding MultiManager method.
func (m *Manager) Size(version uint16, value interface{}) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Size", version, value)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Size expectation.
func (mr *ManagerRecorder) Size(version, value any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Size", reflect.TypeOf((*Manager)(nil).Size), version, value)
}
+2 -3
View File
@@ -252,14 +252,13 @@ func (b *txBuilder) NewCreateAssetTx(
return nil, err
}
codec := Parser.Codec()
states := make([]*txs.InitialState, 0, len(initialState))
for fxIndex, outs := range initialState {
state := &txs.InitialState{
FxIndex: fxIndex,
Outs: outs,
}
state.Sort(codec) // sort the outputs
state.Sort() // sort the outputs
states = append(states, state)
}
@@ -293,7 +292,7 @@ func (b *txBuilder) NewOperationTx(
return nil, err
}
txs.SortOperations(operations, Parser.Codec())
txs.SortOperations(operations)
return &txs.OperationTx{
BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{
NetworkID: b.backend.Context().NetworkID,
+2 -3
View File
@@ -254,7 +254,6 @@ func (b *builder) NewCreateAssetTx(
return nil, err
}
codec := Parser.Codec()
states := make([]*txs.InitialState, 0, len(initialState))
for fxIndex, outs := range initialState {
state := &txs.InitialState{
@@ -262,7 +261,7 @@ func (b *builder) NewCreateAssetTx(
FxID: fxIndexToID[fxIndex],
Outs: outs,
}
state.Sort(codec) // sort the outputs
state.Sort() // sort the outputs
states = append(states, state)
}
@@ -296,7 +295,7 @@ func (b *builder) NewOperationTx(
return nil, err
}
txs.SortOperations(operations, Parser.Codec())
txs.SortOperations(operations)
tx := &txs.OperationTx{
BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{
NetworkID: b.context.NetworkID,
+5 -9
View File
@@ -16,7 +16,6 @@ import (
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/xvm/fxs"
"github.com/luxfi/node/vms/xvm/txs"
"github.com/luxfi/node/wallet/chain/x/builder"
"github.com/luxfi/utxo/nftfx"
"github.com/luxfi/utxo/propertyfx"
"github.com/luxfi/utxo/secp256k1fx"
@@ -219,8 +218,7 @@ func (s *visitor) getOpsSigners(ctx context.Context, sourceChainID ids.ID, ops [
}
func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer) error {
codec := builder.Parser.Codec()
unsignedBytes, err := codec.Marshal(txs.CodecVersion, &tx.Unsigned)
unsignedBytes, err := txs.UnsignedBytes(tx.Unsigned)
if err != nil {
return fmt.Errorf("couldn't marshal unsigned tx: %w", err)
}
@@ -291,10 +289,8 @@ func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer)
}
}
signedBytes, err := codec.Marshal(txs.CodecVersion, tx)
if err != nil {
return fmt.Errorf("couldn't marshal tx: %w", err)
}
tx.SetBytes(unsignedBytes, signedBytes)
return nil
// Rebuild the signed wire bytes (unsigned ‖ fx credential envelopes) and
// bind TxID = hash(signedBytes). The unsigned bytes are already cached, so
// Initialize reuses the exact bytes just signed over.
return tx.Initialize()
}
+5 -8
View File
@@ -219,8 +219,7 @@ func (s *signerVisitor) getOpsSigners(ctx stdcontext.Context, sourceChainID ids.
}
func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer) error {
codec := Parser.Codec()
unsignedBytes, err := codec.Marshal(txs.CodecVersion, &tx.Unsigned)
unsignedBytes, err := txs.UnsignedBytes(tx.Unsigned)
if err != nil {
return fmt.Errorf("couldn't marshal unsigned tx: %w", err)
}
@@ -288,10 +287,8 @@ func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer)
}
}
signedBytes, err := codec.Marshal(txs.CodecVersion, tx)
if err != nil {
return fmt.Errorf("couldn't marshal tx: %w", err)
}
tx.SetBytes(unsignedBytes, signedBytes)
return nil
// Rebuild the signed wire bytes (unsigned ‖ fx credential envelopes) and
// bind TxID = hash(signedBytes). The unsigned bytes are already cached, so
// Initialize reuses the exact bytes just signed over.
return tx.Initialize()
}