Files
node/vms/xvm/txs/executor/semantic_verifier_test.go
zeekayandHanzo Dev b1cc108c9f gofmt
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 01:51:51 -07:00

1201 lines
27 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"context"
"testing"
"github.com/luxfi/mock/gomock"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/database"
"github.com/luxfi/database/memdb"
"github.com/luxfi/database/prefixdb"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/xvm/fxs"
"github.com/luxfi/node/vms/xvm/state"
"github.com/luxfi/node/vms/xvm/state/statemock"
"github.com/luxfi/node/vms/xvm/txs"
"github.com/luxfi/runtime"
"github.com/luxfi/timer/mockable"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
validators "github.com/luxfi/validators"
"github.com/luxfi/vm/chains/atomic"
)
// testSharedMemory adapts atomic.SharedMemory to executor.SharedMemory
type testSharedMemory struct {
sm atomic.SharedMemory
}
func (t *testSharedMemory) Get(peerChainID ids.ID, keys [][]byte) ([][]byte, error) {
return t.sm.Get(peerChainID, keys)
}
func (t *testSharedMemory) Apply(requests map[ids.ID]interface{}, batch ...interface{}) error {
// We don't use Apply in these tests, just Get
return nil
}
func TestSemanticVerifierBaseTx(t *testing.T) {
ctx := context.Background()
cChainID := ids.GenerateTestID()
fxIndex := txs.NewFxIndex()
secpFx := &secp256k1fx.Fx{}
_, err := txs.NewCustomParser(
fxIndex,
new(mockable.Clock),
log.NoLog{},
[]fxs.Fx{
secpFx,
},
)
require.NoError(t, err)
txID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: txID,
OutputIndex: 2,
}
asset := lux.Asset{
ID: ids.GenerateTestID(),
}
inputSigner := secp256k1fx.Input{
SigIndices: []uint32{
0,
},
}
fxInput := secp256k1fx.TransferInput{
Amt: 12345,
Input: inputSigner,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := txs.BaseTx{
BaseTx: lux.BaseTx{
Ins: []*lux.TransferableInput{
&input,
},
},
}
testChainID := ids.GenerateTestID()
backendObj := &Backend{
Ctx: ctx,
Runtime: &runtime.Runtime{
ChainID: ids.GenerateTestID(),
ValidatorState: &testValidatorState{chainID: testChainID},
},
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: secpFx,
},
},
FxIndex: fxIndex,
FeeAssetID: ids.GenerateTestID(),
Bootstrapped: true,
}
require.NoError(t, secpFx.Bootstrapped())
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{
keys[0].Address(),
},
}
output := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
utxo := lux.UTXO{
UTXOID: utxoID,
Asset: asset,
Out: &output,
}
unsignedCreateAssetTx := txs.CreateAssetTx{
States: []*txs.InitialState{{
FxIndex: 0,
}},
}
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
tests := []struct {
name string
stateFunc func(*gomock.Controller) state.Chain
txFunc func(*require.Assertions) *txs.Tx
err error
}{
{
name: "valid",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: nil,
},
{
name: "assetID mismatch",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
utxo := utxo
utxo.Asset.ID = ids.GenerateTestID()
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: errAssetIDMismatch,
},
{
name: "not allowed input feature extension",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
unsignedCreateAssetTx := unsignedCreateAssetTx
unsignedCreateAssetTx.States = nil
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: errIncompatibleFx,
},
{
name: "invalid signature",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[1]},
},
))
return tx
},
err: secp256k1fx.ErrWrongSig,
},
{
name: "missing UTXO",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(nil, database.ErrNotFound)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: database.ErrNotFound,
},
{
name: "invalid UTXO amount",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
output := output
output.Amt--
utxo := utxo
utxo.Out = &output
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: secp256k1fx.ErrMismatchedAmounts,
},
{
name: "not allowed output feature extension",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
unsignedCreateAssetTx := unsignedCreateAssetTx
unsignedCreateAssetTx.States = nil
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
baseTx := baseTx
baseTx.Ins = nil
baseTx.Outs = []*lux.TransferableOutput{
{
Asset: asset,
Out: &output,
},
}
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{},
))
return tx
},
err: errIncompatibleFx,
},
{
name: "unknown asset",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(nil, database.ErrNotFound)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: database.ErrNotFound,
},
{
name: "not an asset",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
tx := txs.Tx{
Unsigned: &baseTx,
}
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&tx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &baseTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: errNotAnAsset,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
state := test.stateFunc(ctrl)
tx := test.txFunc(require)
err := tx.Unsigned.Visit(&SemanticVerifier{
Backend: backendObj,
State: state,
Tx: tx,
})
require.ErrorIs(err, test.err)
})
}
}
func TestSemanticVerifierExportTx(t *testing.T) {
ctx := context.Background()
cChainID := ids.GenerateTestID()
chainID := ids.GenerateTestID()
fxIndex := txs.NewFxIndex()
secpFx := &secp256k1fx.Fx{}
_, err := txs.NewCustomParser(
fxIndex,
new(mockable.Clock),
log.NoLog{},
[]fxs.Fx{
secpFx,
},
)
require.NoError(t, err)
txID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: txID,
OutputIndex: 2,
}
asset := lux.Asset{
ID: ids.GenerateTestID(),
}
inputSigner := secp256k1fx.Input{
SigIndices: []uint32{
0,
},
}
fxInput := secp256k1fx.TransferInput{
Amt: 12345,
Input: inputSigner,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := txs.BaseTx{
BaseTx: lux.BaseTx{
NetworkID: constants.UnitTestID,
BlockchainID: chainID,
Ins: []*lux.TransferableInput{
&input,
},
},
}
exportTx := txs.ExportTx{
BaseTx: baseTx,
DestinationChain: cChainID,
}
backendObj := &Backend{
Ctx: ctx,
Runtime: &runtime.Runtime{
ChainID: chainID, // Use same chainID as baseTx
ValidatorState: &testValidatorState{chainID: chainID},
},
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: secpFx,
},
},
FxIndex: fxIndex,
FeeAssetID: ids.GenerateTestID(),
Bootstrapped: true,
}
require.NoError(t, secpFx.Bootstrapped())
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{
keys[0].Address(),
},
}
output := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
utxo := lux.UTXO{
UTXOID: utxoID,
Asset: asset,
Out: &output,
}
unsignedCreateAssetTx := txs.CreateAssetTx{
States: []*txs.InitialState{{
FxIndex: 0,
}},
}
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
tests := []struct {
name string
stateFunc func(*gomock.Controller) state.Chain
txFunc func(*require.Assertions) *txs.Tx
err error
}{
{
name: "valid",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: nil,
},
{
name: "assetID mismatch",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
utxo := utxo
utxo.Asset.ID = ids.GenerateTestID()
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: errAssetIDMismatch,
},
{
name: "not allowed input feature extension",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
unsignedCreateAssetTx := unsignedCreateAssetTx
unsignedCreateAssetTx.States = nil
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: errIncompatibleFx,
},
{
name: "invalid signature",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[1]},
},
))
return tx
},
err: secp256k1fx.ErrWrongSig,
},
{
name: "missing UTXO",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(nil, database.ErrNotFound)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: database.ErrNotFound,
},
{
name: "invalid UTXO amount",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
output := output
output.Amt--
utxo := utxo
utxo.Out = &output
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: secp256k1fx.ErrMismatchedAmounts,
},
{
name: "not allowed output feature extension",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
unsignedCreateAssetTx := unsignedCreateAssetTx
unsignedCreateAssetTx.States = nil
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
exportTx := exportTx
exportTx.Ins = nil
exportTx.ExportedOuts = []*lux.TransferableOutput{
{
Asset: asset,
Out: &output,
},
}
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{},
))
return tx
},
err: errIncompatibleFx,
},
{
name: "unknown asset",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(nil, database.ErrNotFound)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: database.ErrNotFound,
},
{
name: "not an asset",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
tx := txs.Tx{
Unsigned: &baseTx,
}
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&tx, nil)
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
return tx
},
err: errNotAnAsset,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
state := test.stateFunc(ctrl)
tx := test.txFunc(require)
err := tx.Unsigned.Visit(&SemanticVerifier{
Backend: backendObj,
State: state,
Tx: tx,
})
require.ErrorIs(err, test.err)
})
}
}
// testValidatorState is a simple stub for validators.State used in tests
type testValidatorState struct {
chainID ids.ID // The chain/chain ID this validator state returns
netIDs map[ids.ID]ids.ID
}
var _ validators.State = (*testValidatorState)(nil)
func (t *testValidatorState) GetChainID(_ ids.ID) (ids.ID, error) {
// Returns the chain/chain ID for the given chain
return t.chainID, nil
}
func (t *testValidatorState) GetNetworkID(chainID ids.ID) (ids.ID, error) {
if t.netIDs != nil {
if netID, ok := t.netIDs[chainID]; ok {
return netID, nil
}
}
return t.chainID, nil
}
func (t *testValidatorState) GetValidatorSet(ctx context.Context, height uint64, netID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
return make(map[ids.NodeID]*validators.GetValidatorOutput), nil
}
func (t *testValidatorState) GetCurrentValidators(ctx context.Context, height uint64, netID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
return t.GetValidatorSet(ctx, height, netID)
}
func (t *testValidatorState) GetCurrentHeight(ctx context.Context) (uint64, error) {
return 0, nil
}
func (t *testValidatorState) GetMinimumHeight(ctx context.Context) (uint64, error) {
return 0, nil
}
func (t *testValidatorState) GetWarpValidatorSet(ctx context.Context, height uint64, netID ids.ID) (*validators.WarpSet, error) {
return &validators.WarpSet{
Height: height,
Validators: make(map[ids.NodeID]*validators.WarpValidator),
}, nil
}
func (t *testValidatorState) GetWarpValidatorSets(ctx context.Context, heights []uint64, netIDs []ids.ID) (map[ids.ID]map[uint64]*validators.WarpSet, error) {
result := make(map[ids.ID]map[uint64]*validators.WarpSet)
for _, netID := range netIDs {
result[netID] = make(map[uint64]*validators.WarpSet)
for _, height := range heights {
result[netID][height] = &validators.WarpSet{
Height: height,
Validators: make(map[ids.NodeID]*validators.WarpValidator),
}
}
}
return result, nil
}
func TestSemanticVerifierExportTxDifferentNet(t *testing.T) {
ctrl := gomock.NewController(t)
cChainID := ids.GenerateTestID()
rt := consensustest.Runtime(t, consensustest.XChainID)
// Set up a validator state that returns different network IDs to trigger the error
localNetID := ids.GenerateTestID()
peerNetID := ids.GenerateTestID()
rt.ValidatorState = &testValidatorState{
chainID: localNetID,
netIDs: map[ids.ID]ids.ID{
rt.ChainID: localNetID,
cChainID: peerNetID,
},
}
fxIndex := txs.NewFxIndex()
secpFx := &secp256k1fx.Fx{}
_, err := txs.NewCustomParser(
fxIndex,
new(mockable.Clock),
log.NoLog{},
[]fxs.Fx{
secpFx,
},
)
require.NoError(t, err)
txID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: txID,
OutputIndex: 2,
}
asset := lux.Asset{
ID: ids.GenerateTestID(),
}
inputSigner := secp256k1fx.Input{
SigIndices: []uint32{
0,
},
}
fxInput := secp256k1fx.TransferInput{
Amt: 12345,
Input: inputSigner,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := txs.BaseTx{
BaseTx: lux.BaseTx{
Ins: []*lux.TransferableInput{
&input,
},
},
}
exportTx := txs.ExportTx{
BaseTx: baseTx,
DestinationChain: cChainID,
}
backendObj := &Backend{
Ctx: context.Background(),
Runtime: rt,
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: secpFx,
},
},
FxIndex: fxIndex,
FeeAssetID: ids.GenerateTestID(),
Bootstrapped: true,
}
require.NoError(t, secpFx.Bootstrapped())
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{
keys[0].Address(),
},
}
output := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
utxo := lux.UTXO{
UTXOID: utxoID,
Asset: asset,
Out: &output,
}
unsignedCreateAssetTx := txs.CreateAssetTx{
States: []*txs.InitialState{{
FxIndex: 0,
}},
}
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil)
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil)
tx := &txs.Tx{
Unsigned: &exportTx,
}
require.NoError(t, tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
err = tx.Unsigned.Visit(&SemanticVerifier{
Backend: backendObj,
State: state,
Tx: tx,
})
require.ErrorIs(t, err, verify.ErrMismatchedNetIDs)
}
func TestSemanticVerifierImportTx(t *testing.T) {
// Create Runtime for chain operations
cChainID := ids.GenerateTestID()
chainID := ids.GenerateTestID()
_ = consensustest.Runtime(t, chainID)
ctx := context.Background() // Use standard context for Backend
m := atomic.NewMemory(prefixdb.New([]byte{0}, memdb.New()))
fxIndex := txs.NewFxIndex()
fx := &secp256k1fx.Fx{}
_, err := txs.NewCustomParser(
fxIndex,
new(mockable.Clock),
log.NoLog{},
[]fxs.Fx{
fx,
},
)
require.NoError(t, err)
utxoID := lux.UTXOID{
TxID: ids.GenerateTestID(),
OutputIndex: 2,
}
asset := lux.Asset{
ID: ids.GenerateTestID(),
}
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{
keys[0].Address(),
},
}
baseTx := txs.BaseTx{
BaseTx: lux.BaseTx{
NetworkID: constants.UnitTestID,
BlockchainID: chainID,
Outs: []*lux.TransferableOutput{{
Asset: asset,
Out: &secp256k1fx.TransferOutput{
Amt: 1000,
OutputOwners: outputOwners,
},
}},
},
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &secp256k1fx.TransferInput{
Amt: 12345,
Input: secp256k1fx.Input{
SigIndices: []uint32{0},
},
},
}
unsignedImportTx := txs.ImportTx{
BaseTx: baseTx,
SourceChain: cChainID,
ImportedIns: []*lux.TransferableInput{
&input,
},
}
importTx := &txs.Tx{
Unsigned: &unsignedImportTx,
}
require.NoError(t, importTx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[0]},
},
))
backendObj := &Backend{
Ctx: ctx,
Runtime: &runtime.Runtime{
ChainID: chainID, // Use same chainID as baseTx
ValidatorState: &testValidatorState{chainID: chainID},
},
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: fx,
},
},
FxIndex: fxIndex,
FeeAssetID: ids.GenerateTestID(),
Bootstrapped: true,
SharedMemory: &testSharedMemory{sm: m.NewSharedMemory(chainID)},
}
require.NoError(t, fx.Bootstrapped())
output := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
utxo := lux.UTXO{
UTXOID: utxoID,
Asset: asset,
Out: &output,
}
utxoBytes, err := utxo.WireBytes()
require.NoError(t, err)
peerSharedMemory := m.NewSharedMemory(cChainID)
inputID := utxo.InputID()
require.NoError(t, peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{chainID: {PutRequests: []*atomic.Element{{
Key: inputID[:],
Value: utxoBytes,
Traits: [][]byte{
keys[0].PublicKey().Address().Bytes(),
},
}}}}))
unsignedCreateAssetTx := txs.CreateAssetTx{
States: []*txs.InitialState{{
FxIndex: 0,
}},
}
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
tests := []struct {
name string
stateFunc func(*gomock.Controller) state.Chain
txFunc func(*require.Assertions) *txs.Tx
expectedErr error
}{
{
name: "valid",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes()
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes()
return state
},
txFunc: func(*require.Assertions) *txs.Tx {
return importTx
},
expectedErr: nil,
},
{
name: "not allowed input feature extension",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
unsignedCreateAssetTx := unsignedCreateAssetTx
unsignedCreateAssetTx.States = nil
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes()
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes()
return state
},
txFunc: func(*require.Assertions) *txs.Tx {
return importTx
},
expectedErr: errIncompatibleFx,
},
{
name: "invalid signature",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes()
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes()
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
tx := &txs.Tx{
Unsigned: &unsignedImportTx,
}
require.NoError(tx.SignSECP256K1Fx(
[][]*secp256k1.PrivateKey{
{keys[1]},
},
))
return tx
},
expectedErr: secp256k1fx.ErrWrongSig,
},
{
name: "not allowed output feature extension",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
unsignedCreateAssetTx := unsignedCreateAssetTx
unsignedCreateAssetTx.States = nil
createAssetTx := txs.Tx{
Unsigned: &unsignedCreateAssetTx,
}
state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes()
return state
},
txFunc: func(require *require.Assertions) *txs.Tx {
importTx := unsignedImportTx
importTx.Ins = nil
importTx.ImportedIns = []*lux.TransferableInput{
&input,
}
tx := &txs.Tx{
Unsigned: &importTx,
}
require.NoError(tx.SignSECP256K1Fx(
nil,
))
return tx
},
expectedErr: errIncompatibleFx,
},
{
name: "unknown asset",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes()
state.EXPECT().GetTx(asset.ID).Return(nil, database.ErrNotFound)
return state
},
txFunc: func(*require.Assertions) *txs.Tx {
return importTx
},
expectedErr: database.ErrNotFound,
},
{
name: "not an asset",
stateFunc: func(ctrl *gomock.Controller) state.Chain {
state := statemock.NewChain(ctrl)
tx := txs.Tx{
Unsigned: &baseTx,
}
state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes()
state.EXPECT().GetTx(asset.ID).Return(&tx, nil)
return state
},
txFunc: func(*require.Assertions) *txs.Tx {
return importTx
},
expectedErr: errNotAnAsset,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
state := test.stateFunc(ctrl)
tx := test.txFunc(require)
err := tx.Unsigned.Visit(&SemanticVerifier{
Backend: backendObj,
State: state,
Tx: tx,
})
require.ErrorIs(err, test.expectedErr)
})
}
}