mirror of
https://github.com/luxfi/utxo.git
synced 2026-10-30 15:13:57 +00:00
Adds github.com/luxfi/utxo/wire package + per-fx wire.go adapters so
that luxfi/node's vms/{platformvm,xvm}/txs/codec.go can be deleted.
wire/ package mirrors luxfi/node/vms/wire — every fxs primitive carries
a (TypeKind, ShapeKind) discriminator pair on its wire envelope, then
a ZAP message describing its fields. Decomposes the legacy
codec.Manager slot map (which braided "fx family" and "primitive shape"
into a single dense uint32 slot id) into two orthogonal 1-byte tags.
PQ-specific schemas (pq_output_owners, pq_transfer_output,
pq_mint_output) handle the mldsafx/slhdsafx case where addresses are
full PQ pubkeys (1312-2592 bytes) rather than 20-byte hashed ShortIDs.
The wire layer is stride-agnostic — the per-fx wire.go resolves
SecurityLevel → pubkey stride before parsing.
Per-fx wire.go adapters provide (value T).Bytes() []byte (Go type →
wire envelope) and Wrap*(b []byte) (*T, error) (wire envelope → Go
type). The Initialize(vm)/codec.Manager.RegisterType chain stays in
place pending downstream coordination; this commit gets the wire/
package + adapters in so consumer agents (platformvm/warp,
platformvm/state, xvm) can pin a SHA and start their migrations.
UTXO.WireBytes() builds the outer envelope by calling the polymorphic
Out's Bytes() (any fxs primitive implementing wireSerializable).
WrapUTXOBytes(b) parses the outer envelope; the inner output is
dispatched by the caller via OutputDiscriminator() → fx-package
WrapTransferOutput / WrapMintOutput / WrapAttestationOutput.
Tests: 35 round-trip tests across 9 packages. go vet clean. go test
-race clean. gofmt clean.
Activation: 2025-12-25T16:20:00-08:00 (unix 1766708400). No
backwards compat — pre-activation legacy codec blobs are a
protocol-violation reject after activation.
100 lines
3.0 KiB
Go
100 lines
3.0 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package utxo_test
|
|
|
|
// External test package — exercises both the root utxo package and the
|
|
// fx packages without creating an import cycle.
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/vm/components/verify"
|
|
|
|
"github.com/luxfi/utxo"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
"github.com/luxfi/utxo/wire"
|
|
)
|
|
|
|
// Test that a UTXO carrying a *secp256k1fx.TransferOutput round-trips
|
|
// through the wire envelope cleanly.
|
|
func TestUTXO_WireBytes_Secp256k1_TransferOutput_RoundTrip(t *testing.T) {
|
|
addr := ids.ShortID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
|
txID := ids.ID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
|
|
assetID := ids.ID{32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
|
|
|
|
inOut := &secp256k1fx.TransferOutput{
|
|
Amt: 1_000_000,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Locktime: 42,
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{addr},
|
|
},
|
|
}
|
|
in := &utxo.UTXO{
|
|
UTXOID: utxo.UTXOID{TxID: txID, OutputIndex: 7},
|
|
Asset: utxo.Asset{ID: assetID},
|
|
Out: inOut,
|
|
}
|
|
|
|
envelope, err := in.WireBytes()
|
|
if err != nil {
|
|
t.Fatalf("WireBytes: %v", err)
|
|
}
|
|
|
|
got, err := utxo.WrapUTXOBytes(envelope)
|
|
if err != nil {
|
|
t.Fatalf("WrapUTXOBytes: %v", err)
|
|
}
|
|
if got.TxID() != txID {
|
|
t.Errorf("TxID: got %x, want %x", got.TxID(), txID)
|
|
}
|
|
if got.OutputIndex() != 7 {
|
|
t.Errorf("OutputIndex: got %d, want 7", got.OutputIndex())
|
|
}
|
|
if got.AssetID() != assetID {
|
|
t.Errorf("AssetID: got %x, want %x", got.AssetID(), assetID)
|
|
}
|
|
|
|
tk, sk := got.OutputDiscriminator()
|
|
if tk != wire.TypeKindSecp256k1 {
|
|
t.Errorf("OutputDiscriminator TypeKind: got %x, want %x", tk, wire.TypeKindSecp256k1)
|
|
}
|
|
if sk != wire.ShapeKindTransferOutput {
|
|
t.Errorf("OutputDiscriminator ShapeKind: got %x, want %x", sk, wire.ShapeKindTransferOutput)
|
|
}
|
|
|
|
// Dispatch on the discriminator into the fx-package WrapTransferOutput.
|
|
gotOut, err := secp256k1fx.WrapTransferOutput(got.OutputBytes())
|
|
if err != nil {
|
|
t.Fatalf("WrapTransferOutput: %v", err)
|
|
}
|
|
if gotOut.Amt != inOut.Amt {
|
|
t.Errorf("Out.Amt: got %d, want %d", gotOut.Amt, inOut.Amt)
|
|
}
|
|
if !gotOut.OutputOwners.Equals(&inOut.OutputOwners) {
|
|
t.Errorf("Out.OutputOwners mismatch")
|
|
}
|
|
}
|
|
|
|
// Test that WireBytes refuses a non-wire-serializable Out.
|
|
func TestUTXO_WireBytes_RejectsNonWireSerializableOut(t *testing.T) {
|
|
in := &utxo.UTXO{
|
|
UTXOID: utxo.UTXOID{TxID: ids.ID{1}, OutputIndex: 0},
|
|
Asset: utxo.Asset{ID: ids.ID{2}},
|
|
Out: unknownOut{},
|
|
}
|
|
if _, err := in.WireBytes(); err != utxo.ErrUTXOOutNotWireSerializable {
|
|
t.Errorf("WireBytes: got err=%v, want ErrUTXOOutNotWireSerializable", err)
|
|
}
|
|
}
|
|
|
|
// unknownOut is a verify.State that does NOT implement Bytes() — used to
|
|
// confirm WireBytes refuses unknown types cleanly.
|
|
type unknownOut struct {
|
|
verify.IsState
|
|
}
|
|
|
|
func (unknownOut) Verify() error { return nil }
|