mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
xvm: drop reflect from fx dispatch — closed-sum type switch + dense tag array
The fx set is a CLOSED sum type (secp256k1fx | nftfx | propertyfx), fixed at
compile time — no hot-loading, no genesis-configured fx. So the runtime
'which fx owns this value' dispatch needs no reflection: it is a total match on
the variant tag. Replaced reflect.TypeOf(val) + map[reflect.Type]int with:
- fxKindOf(val): a Go type switch over the closed set of fx primitive types
(compiler-checked exhaustive; lowers to a jump on the interface type tag),
returning the value's wire.TypeKind — the same family tag the wire envelope
already carries.
- FxIndex: a dense [16]int array indexed by that TypeKind (one bounds-checked
load; -1 = unregistered), filled by the SAME fx.(type) switch NewCustomParser
already ran — no separate reflect registration.
getFx (semantic verifier + tx_init) is now fxKindOf → array index: zero reflect,
zero map-hash, compile-time-checked. Deleted registerFxTypes + all
map[reflect.Type]int fields/params (parser, block/parser, vm, backend). Node-only
(uses already-imported utxo fx types + wire.TypeKind); no dep cascade.
Full xvm suite green (11 pkgs — secp/nft/property verify dispatch exercised).
This removes the LAST reflection from the X-chain tx/verify path.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
@@ -70,7 +70,8 @@
|
||||
"v1.36.5",
|
||||
"v1.36.6",
|
||||
"v1.36.7",
|
||||
"v1.36.8"
|
||||
"v1.36.8",
|
||||
"v1.36.9"
|
||||
],
|
||||
"41": [
|
||||
"v1.13.2"
|
||||
|
||||
@@ -77,7 +77,7 @@ var (
|
||||
const (
|
||||
defaultMajor = 1
|
||||
defaultMinor = 36
|
||||
defaultPatch = 8
|
||||
defaultPatch = 9
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/node/vms/xvm/fxs"
|
||||
"github.com/luxfi/node/vms/xvm/txs"
|
||||
@@ -36,12 +34,12 @@ func NewParser(fxs []fxs.Fx) (Parser, error) {
|
||||
}
|
||||
|
||||
func NewCustomParser(
|
||||
typeToFxIndex map[reflect.Type]int,
|
||||
fxIndex *txs.FxIndex,
|
||||
clock *mockable.Clock,
|
||||
log log.Logger,
|
||||
fxs []fxs.Fx,
|
||||
) (Parser, error) {
|
||||
p, err := txs.NewCustomParser(typeToFxIndex, clock, log, fxs)
|
||||
p, err := txs.NewCustomParser(fxIndex, clock, log, fxs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+3
-3
@@ -130,7 +130,7 @@ func (s *Service) GetBlock(_ *http.Request, args *apitypes.GetBlockArgs, reply *
|
||||
for _, tx := range block.Txs() {
|
||||
err := tx.Unsigned.Visit(&txInit{
|
||||
tx: tx,
|
||||
typeToFxIndex: s.vm.typeToFxIndex,
|
||||
fxIndex: s.vm.fxIndex,
|
||||
fxs: s.vm.fxs,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -187,7 +187,7 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *apitypes.GetBlockByHei
|
||||
for _, tx := range block.Txs() {
|
||||
err := tx.Unsigned.Visit(&txInit{
|
||||
tx: tx,
|
||||
typeToFxIndex: s.vm.typeToFxIndex,
|
||||
fxIndex: s.vm.fxIndex,
|
||||
fxs: s.vm.fxs,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -408,7 +408,7 @@ func (s *Service) GetTx(_ *http.Request, args *apitypes.GetTxArgs, reply *apityp
|
||||
if args.Encoding == formatting.JSON {
|
||||
err = tx.Unsigned.Visit(&txInit{
|
||||
tx: tx,
|
||||
typeToFxIndex: s.vm.typeToFxIndex,
|
||||
fxIndex: s.vm.fxIndex,
|
||||
fxs: s.vm.fxs,
|
||||
})
|
||||
result = tx
|
||||
|
||||
+5
-8
@@ -4,8 +4,6 @@
|
||||
package xvm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/luxfi/node/vms/xvm/fxs"
|
||||
"github.com/luxfi/node/vms/xvm/txs"
|
||||
)
|
||||
@@ -14,15 +12,14 @@ var _ txs.Visitor = (*txInit)(nil)
|
||||
|
||||
// txInit initializes FxID where required
|
||||
type txInit struct {
|
||||
tx *txs.Tx
|
||||
typeToFxIndex map[reflect.Type]int
|
||||
fxs []*fxs.ParsedFx
|
||||
tx *txs.Tx
|
||||
fxIndex *txs.FxIndex
|
||||
fxs []*fxs.ParsedFx
|
||||
}
|
||||
|
||||
func (t *txInit) getFx(val interface{}) (int, error) {
|
||||
valType := reflect.TypeOf(val)
|
||||
fx, exists := t.typeToFxIndex[valType]
|
||||
if !exists {
|
||||
fx, ok := t.fxIndex.GetFx(val)
|
||||
if !ok {
|
||||
return 0, errUnknownFx
|
||||
}
|
||||
return fx, nil
|
||||
|
||||
@@ -5,13 +5,13 @@ package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/node/vms/components/verify"
|
||||
"github.com/luxfi/node/vms/xvm/config"
|
||||
"github.com/luxfi/node/vms/xvm/fxs"
|
||||
"github.com/luxfi/node/vms/xvm/txs"
|
||||
"github.com/luxfi/runtime"
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ type Backend struct {
|
||||
Runtime *runtime.Runtime
|
||||
Config *config.Config
|
||||
Fxs []*fxs.ParsedFx
|
||||
TypeToFxIndex map[reflect.Type]int
|
||||
FxIndex *txs.FxIndex
|
||||
// Note: FeeAssetID may be different than ctx.UTXOAssetID if this XVM is
|
||||
// running in a chain.
|
||||
FeeAssetID ids.ID
|
||||
|
||||
@@ -6,7 +6,6 @@ package executor
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
lux "github.com/luxfi/utxo"
|
||||
@@ -246,9 +245,8 @@ func (v *SemanticVerifier) verifyFxUsage(
|
||||
}
|
||||
|
||||
func (v *SemanticVerifier) getFx(val interface{}) (int, error) {
|
||||
valType := reflect.TypeOf(val)
|
||||
fx, exists := v.TypeToFxIndex[valType]
|
||||
if !exists {
|
||||
fx, ok := v.FxIndex.GetFx(val)
|
||||
if !ok {
|
||||
return 0, errUnknownFx
|
||||
}
|
||||
return fx, nil
|
||||
|
||||
@@ -5,7 +5,6 @@ package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/mock/gomock"
|
||||
@@ -50,10 +49,10 @@ func TestSemanticVerifierBaseTx(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cChainID := ids.GenerateTestID()
|
||||
|
||||
typeToFxIndex := make(map[reflect.Type]int)
|
||||
fxIndex := txs.NewFxIndex()
|
||||
secpFx := &secp256k1fx.Fx{}
|
||||
_, err := txs.NewCustomParser(
|
||||
typeToFxIndex,
|
||||
fxIndex,
|
||||
new(mockable.Clock),
|
||||
log.NoLog{},
|
||||
[]fxs.Fx{
|
||||
@@ -107,7 +106,7 @@ func TestSemanticVerifierBaseTx(t *testing.T) {
|
||||
Fx: secpFx,
|
||||
},
|
||||
},
|
||||
TypeToFxIndex: typeToFxIndex,
|
||||
FxIndex: fxIndex,
|
||||
FeeAssetID: ids.GenerateTestID(),
|
||||
Bootstrapped: true,
|
||||
}
|
||||
@@ -404,10 +403,10 @@ func TestSemanticVerifierExportTx(t *testing.T) {
|
||||
cChainID := ids.GenerateTestID()
|
||||
chainID := ids.GenerateTestID()
|
||||
|
||||
typeToFxIndex := make(map[reflect.Type]int)
|
||||
fxIndex := txs.NewFxIndex()
|
||||
secpFx := &secp256k1fx.Fx{}
|
||||
_, err := txs.NewCustomParser(
|
||||
typeToFxIndex,
|
||||
fxIndex,
|
||||
new(mockable.Clock),
|
||||
log.NoLog{},
|
||||
[]fxs.Fx{
|
||||
@@ -466,7 +465,7 @@ func TestSemanticVerifierExportTx(t *testing.T) {
|
||||
Fx: secpFx,
|
||||
},
|
||||
},
|
||||
TypeToFxIndex: typeToFxIndex,
|
||||
FxIndex: fxIndex,
|
||||
FeeAssetID: ids.GenerateTestID(),
|
||||
Bootstrapped: true,
|
||||
}
|
||||
@@ -834,10 +833,10 @@ func TestSemanticVerifierExportTxDifferentNet(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
typeToFxIndex := make(map[reflect.Type]int)
|
||||
fxIndex := txs.NewFxIndex()
|
||||
secpFx := &secp256k1fx.Fx{}
|
||||
_, err := txs.NewCustomParser(
|
||||
typeToFxIndex,
|
||||
fxIndex,
|
||||
new(mockable.Clock),
|
||||
log.NoLog{},
|
||||
[]fxs.Fx{
|
||||
@@ -891,7 +890,7 @@ func TestSemanticVerifierExportTxDifferentNet(t *testing.T) {
|
||||
Fx: secpFx,
|
||||
},
|
||||
},
|
||||
TypeToFxIndex: typeToFxIndex,
|
||||
FxIndex: fxIndex,
|
||||
FeeAssetID: ids.GenerateTestID(),
|
||||
Bootstrapped: true,
|
||||
}
|
||||
@@ -951,10 +950,10 @@ func TestSemanticVerifierImportTx(t *testing.T) {
|
||||
ctx := context.Background() // Use standard context for Backend
|
||||
m := atomic.NewMemory(prefixdb.New([]byte{0}, memdb.New()))
|
||||
|
||||
typeToFxIndex := make(map[reflect.Type]int)
|
||||
fxIndex := txs.NewFxIndex()
|
||||
fx := &secp256k1fx.Fx{}
|
||||
_, err := txs.NewCustomParser(
|
||||
typeToFxIndex,
|
||||
fxIndex,
|
||||
new(mockable.Clock),
|
||||
log.NoLog{},
|
||||
[]fxs.Fx{
|
||||
@@ -1030,7 +1029,7 @@ func TestSemanticVerifierImportTx(t *testing.T) {
|
||||
Fx: fx,
|
||||
},
|
||||
},
|
||||
TypeToFxIndex: typeToFxIndex,
|
||||
FxIndex: fxIndex,
|
||||
FeeAssetID: ids.GenerateTestID(),
|
||||
Bootstrapped: true,
|
||||
SharedMemory: &testSharedMemory{sm: m.NewSharedMemory(chainID)},
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package txs
|
||||
|
||||
import (
|
||||
"github.com/luxfi/utxo/nftfx"
|
||||
"github.com/luxfi/utxo/propertyfx"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
"github.com/luxfi/utxo/wire"
|
||||
)
|
||||
|
||||
// fx dispatch — reflection-free. The set of fx primitives is a CLOSED sum type
|
||||
// (a finite coproduct of statically-known variants: secp256k1fx | nftfx |
|
||||
// propertyfx). Dispatch is therefore a total match on the variant tag — Go's
|
||||
// type switch, which the compiler lowers to a jump on the interface type tag,
|
||||
// exactly as an ADT `case` compiles. The tag then indexes a dense array. No
|
||||
// reflect.TypeOf, no map[reflect.Type]int: the mechanism is compile-time-typed,
|
||||
// the lookup is a single indexed load.
|
||||
|
||||
// numTypeKinds bounds the dense fx-family enum (wire.TypeKind values 0x00..0x09,
|
||||
// rounded up). FxIndex indexes by that byte directly.
|
||||
const numTypeKinds = 16
|
||||
|
||||
// FxIndex maps a wire.TypeKind (fx family) to its position in a parser's fx
|
||||
// list. Array-backed: Get is one bounds-checked load, no hashing. A slot of -1
|
||||
// means no fx of that family is registered.
|
||||
type FxIndex struct {
|
||||
byKind [numTypeKinds]int
|
||||
}
|
||||
|
||||
// NewFxIndex returns an FxIndex with every family unregistered (-1).
|
||||
func NewFxIndex() *FxIndex {
|
||||
fi := &FxIndex{}
|
||||
for i := range fi.byKind {
|
||||
fi.byKind[i] = -1
|
||||
}
|
||||
return fi
|
||||
}
|
||||
|
||||
// Set records that the fx family `kind` lives at list position `idx`.
|
||||
func (fi *FxIndex) Set(kind wire.TypeKind, idx int) {
|
||||
if int(kind) < numTypeKinds {
|
||||
fi.byKind[kind] = idx
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the fx-list index for `kind`, or (0, false) when unregistered.
|
||||
func (fi *FxIndex) Get(kind wire.TypeKind) (int, bool) {
|
||||
if int(kind) >= numTypeKinds {
|
||||
return 0, false
|
||||
}
|
||||
idx := fi.byKind[kind]
|
||||
if idx < 0 {
|
||||
return 0, false
|
||||
}
|
||||
return idx, true
|
||||
}
|
||||
|
||||
// GetFx maps a parsed fx value to its fx-list index — the reflection-free
|
||||
// replacement for `reflect.TypeOf(val)` + `map[reflect.Type]int`. It reads the
|
||||
// value's family via the compile-time-checked fxKindOf match, then indexes.
|
||||
func (fi *FxIndex) GetFx(val interface{}) (int, bool) {
|
||||
kind, ok := fxKindOf(val)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return fi.Get(kind)
|
||||
}
|
||||
|
||||
// fxKindOf returns the fx family that owns a parsed fx primitive value — a
|
||||
// total match over the closed set of fx output/input/credential/operation
|
||||
// types. Adding a new (fx, shape) is a new case here; the compiler checks the
|
||||
// types are real. No reflection.
|
||||
func fxKindOf(val interface{}) (wire.TypeKind, bool) {
|
||||
switch val.(type) {
|
||||
case *secp256k1fx.TransferInput,
|
||||
*secp256k1fx.TransferOutput,
|
||||
*secp256k1fx.MintOutput,
|
||||
*secp256k1fx.MintOperation,
|
||||
*secp256k1fx.Credential:
|
||||
return wire.TypeKindSecp256k1, true
|
||||
case *nftfx.MintOutput,
|
||||
*nftfx.TransferOutput,
|
||||
*nftfx.MintOperation,
|
||||
*nftfx.TransferOperation,
|
||||
*nftfx.Credential:
|
||||
return wire.TypeKindNFT, true
|
||||
case *propertyfx.MintOutput,
|
||||
*propertyfx.OwnedOutput,
|
||||
*propertyfx.MintOperation,
|
||||
*propertyfx.BurnOperation,
|
||||
*propertyfx.Credential:
|
||||
return wire.TypeKindProperty, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
+9
-34
@@ -10,7 +10,6 @@ package txs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/log"
|
||||
@@ -39,7 +38,7 @@ type parser struct {
|
||||
|
||||
func NewParser(fxs []fxs.Fx) (Parser, error) {
|
||||
return NewCustomParser(
|
||||
make(map[reflect.Type]int),
|
||||
NewFxIndex(),
|
||||
&mockable.Clock{},
|
||||
log.Noop(),
|
||||
fxs,
|
||||
@@ -47,7 +46,7 @@ func NewParser(fxs []fxs.Fx) (Parser, error) {
|
||||
}
|
||||
|
||||
func NewCustomParser(
|
||||
typeToFxIndex map[reflect.Type]int,
|
||||
fxIndex *FxIndex,
|
||||
clock *mockable.Clock,
|
||||
logger log.Logger,
|
||||
fxList []fxs.Fx,
|
||||
@@ -57,46 +56,22 @@ func NewCustomParser(
|
||||
if err := fx.Initialize(vm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Populate the fx-usage index (concrete fx type -> fx index) that the
|
||||
// semantic verifier's getFx and tx_init.InitializeFx consult. This is
|
||||
// verification policy, distinct from wire decoding (which is envelope
|
||||
// dispatched); it is a plain map fill, no codec.
|
||||
// Record the fx family -> list-position mapping the semantic verifier's
|
||||
// getFx and tx_init consult. Keyed by wire.TypeKind (the family tag),
|
||||
// filled by the SAME closed-set match used everywhere else — no
|
||||
// reflect.TypeOf, no map[reflect.Type]int.
|
||||
switch fx.(type) {
|
||||
case *secp256k1fx.Fx:
|
||||
registerFxTypes(typeToFxIndex, i,
|
||||
&secp256k1fx.TransferInput{},
|
||||
&secp256k1fx.MintOutput{},
|
||||
&secp256k1fx.TransferOutput{},
|
||||
&secp256k1fx.MintOperation{},
|
||||
&secp256k1fx.Credential{},
|
||||
)
|
||||
fxIndex.Set(wire.TypeKindSecp256k1, i)
|
||||
case *nftfx.Fx:
|
||||
registerFxTypes(typeToFxIndex, i,
|
||||
&nftfx.MintOutput{},
|
||||
&nftfx.TransferOutput{},
|
||||
&nftfx.MintOperation{},
|
||||
&nftfx.TransferOperation{},
|
||||
&nftfx.Credential{},
|
||||
)
|
||||
fxIndex.Set(wire.TypeKindNFT, i)
|
||||
case *propertyfx.Fx:
|
||||
registerFxTypes(typeToFxIndex, i,
|
||||
&propertyfx.MintOutput{},
|
||||
&propertyfx.OwnedOutput{},
|
||||
&propertyfx.MintOperation{},
|
||||
&propertyfx.BurnOperation{},
|
||||
&propertyfx.Credential{},
|
||||
)
|
||||
fxIndex.Set(wire.TypeKindProperty, i)
|
||||
}
|
||||
}
|
||||
return &parser{fxs: fxList}, nil
|
||||
}
|
||||
|
||||
func registerFxTypes(m map[reflect.Type]int, index int, vals ...interface{}) {
|
||||
for _, v := range vals {
|
||||
m[reflect.TypeOf(v)] = index
|
||||
}
|
||||
}
|
||||
|
||||
// Parse decodes a signed X-chain tx from its wire bytes. It is the codec-free,
|
||||
// parser-instance-free entry point used by the block layer and any consumer
|
||||
// holding raw tx bytes; fx credential dispatch is envelope-based (stateless).
|
||||
|
||||
+4
-5
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -147,7 +146,7 @@ type VM struct {
|
||||
baseDB database.Database
|
||||
db *versiondb.Database
|
||||
|
||||
typeToFxIndex map[reflect.Type]int
|
||||
fxIndex *txs.FxIndex
|
||||
fxs []*extensions.ParsedFx
|
||||
|
||||
walletService WalletService
|
||||
@@ -321,9 +320,9 @@ func (vm *VM) initialize(
|
||||
}
|
||||
}
|
||||
|
||||
vm.typeToFxIndex = map[reflect.Type]int{}
|
||||
vm.fxIndex = txs.NewFxIndex()
|
||||
vm.parser, err = block.NewCustomParser(
|
||||
vm.typeToFxIndex,
|
||||
vm.fxIndex,
|
||||
&vm.clock,
|
||||
vm.log,
|
||||
typedFxs,
|
||||
@@ -375,7 +374,7 @@ func (vm *VM) initialize(
|
||||
Runtime: vm.consensusRuntime,
|
||||
Config: &vm.Config,
|
||||
Fxs: vm.fxs,
|
||||
TypeToFxIndex: vm.typeToFxIndex,
|
||||
FxIndex: vm.fxIndex,
|
||||
FeeAssetID: vm.feeAssetID,
|
||||
Bootstrapped: false,
|
||||
SharedMemory: vm.SharedMemory,
|
||||
|
||||
Reference in New Issue
Block a user