mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
One place, one way to define UTXO primitives. Delete the in-tree duplicate at node/vms/components/lux/ and import the standalone luxfi/utxo package with a 'lux' alias so existing call-sites (lux.UTXO, lux.TransferableInput, etc.) remain unchanged in consumers. Files changed: 167 imports rewritten, 2 directories deleted. Build still green on the whole tree except pre-existing examples/multi-network QChainMainnetID issue (unrelated). Next: rename luxfi/utxo/luxmock → utxomock for full brand-neutrality; that is a separate PR since it requires touching all consumer alias names.
365 lines
9.3 KiB
Go
365 lines
9.3 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package network
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/metric"
|
|
"github.com/luxfi/mock/gomock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
consensustest "github.com/luxfi/consensus/test/helpers"
|
|
validators "github.com/luxfi/validators"
|
|
"github.com/luxfi/crypto/bls"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/math/set"
|
|
lux "github.com/luxfi/utxo"
|
|
"github.com/luxfi/node/vms/platformvm/config"
|
|
"github.com/luxfi/node/vms/platformvm/txs"
|
|
"github.com/luxfi/node/vms/txs/mempool"
|
|
"github.com/luxfi/warp"
|
|
|
|
pmempool "github.com/luxfi/node/vms/platformvm/txs/mempool"
|
|
)
|
|
|
|
// testSender implements warp.Sender for testing with optional call tracking
|
|
type testSender struct {
|
|
sendGossipCalled bool
|
|
}
|
|
|
|
var _ warp.Sender = (*testSender)(nil)
|
|
|
|
func (t *testSender) SendRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (t *testSender) SendResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, responseBytes []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (t *testSender) SendError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
|
|
return nil
|
|
}
|
|
|
|
func (t *testSender) SendGossip(ctx context.Context, config warp.SendConfig, gossipBytes []byte) error {
|
|
t.sendGossipCalled = true
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
errTest = errors.New("test error")
|
|
|
|
testConfig = config.Network{
|
|
MaxValidatorSetStaleness: time.Second,
|
|
TargetGossipSize: 1,
|
|
PushGossipNumValidators: 1,
|
|
PushGossipNumPeers: 0,
|
|
PushRegossipNumValidators: 1,
|
|
PushRegossipNumPeers: 0,
|
|
PushGossipDiscardedCacheSize: 1,
|
|
PushGossipMaxRegossipFrequency: time.Second,
|
|
PushGossipFrequency: time.Second,
|
|
PullGossipPollSize: 1,
|
|
PullGossipFrequency: time.Second,
|
|
PullGossipThrottlingPeriod: time.Second,
|
|
PullGossipThrottlingLimit: 1,
|
|
ExpectedBloomFilterElements: 10,
|
|
ExpectedBloomFilterFalsePositiveProbability: .1,
|
|
MaxBloomFilterFalsePositiveProbability: .5,
|
|
}
|
|
)
|
|
|
|
// mockValidatorState implements validators.State for testing
|
|
type mockValidatorState struct {
|
|
height uint64
|
|
validators map[ids.NodeID]*validators.GetValidatorOutput
|
|
}
|
|
|
|
func (m *mockValidatorState) GetMinimumHeight(ctx context.Context) (uint64, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
func (m *mockValidatorState) GetCurrentHeight(ctx context.Context) (uint64, error) {
|
|
return m.height, nil
|
|
}
|
|
|
|
func (m *mockValidatorState) GetValidatorSet(
|
|
ctx context.Context,
|
|
height uint64,
|
|
netID ids.ID,
|
|
) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
|
|
return m.validators, nil
|
|
}
|
|
|
|
func (m *mockValidatorState) GetCurrentValidators(
|
|
ctx context.Context,
|
|
height uint64,
|
|
netID ids.ID,
|
|
) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
|
|
return m.validators, nil
|
|
}
|
|
|
|
// GetCurrentValidatorOutput represents a validator output
|
|
type GetCurrentValidatorOutput struct {
|
|
NodeID ids.NodeID
|
|
PublicKey *bls.PublicKey
|
|
Weight uint64
|
|
}
|
|
|
|
func (m *mockValidatorState) GetCurrentValidatorSet(
|
|
ctx context.Context,
|
|
netID ids.ID,
|
|
) (map[ids.ID]*GetCurrentValidatorOutput, uint64, error) {
|
|
// Not used in this test
|
|
return nil, m.height, nil
|
|
}
|
|
|
|
func (m *mockValidatorState) GetWarpValidatorSet(
|
|
ctx context.Context,
|
|
height uint64,
|
|
netID ids.ID,
|
|
) (*validators.WarpSet, error) {
|
|
// Not used in this test
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *mockValidatorState) GetWarpValidatorSets(
|
|
ctx context.Context,
|
|
heights []uint64,
|
|
netIDs []ids.ID,
|
|
) (map[ids.ID]map[uint64]*validators.WarpSet, error) {
|
|
// Not used in this test
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *mockValidatorState) GetChainID(netID ids.ID) (ids.ID, error) {
|
|
return ids.Empty, nil
|
|
}
|
|
|
|
func (m *mockValidatorState) GetNetworkID(chainID ids.ID) (ids.ID, error) {
|
|
return ids.Empty, nil
|
|
}
|
|
|
|
var _ TxVerifier = (*testTxVerifier)(nil)
|
|
|
|
type testTxVerifier struct {
|
|
err error
|
|
}
|
|
|
|
func (t testTxVerifier) VerifyTx(*txs.Tx) error {
|
|
return t.err
|
|
}
|
|
|
|
func TestNetworkIssueTxFromRPC(t *testing.T) {
|
|
type test struct {
|
|
name string
|
|
mempool *pmempool.Mempool
|
|
txVerifier testTxVerifier
|
|
appSenderFunc func(*gomock.Controller) warp.Sender
|
|
tx *txs.Tx
|
|
expectedErr error
|
|
}
|
|
|
|
tests := []test{
|
|
{
|
|
name: "mempool has transaction",
|
|
mempool: func() *pmempool.Mempool {
|
|
mempool, err := pmempool.New("", metric.NewRegistry())
|
|
require.NoError(t, err)
|
|
require.NoError(t, mempool.Add(&txs.Tx{Unsigned: &txs.BaseTx{}}))
|
|
return mempool
|
|
}(),
|
|
appSenderFunc: func(ctrl *gomock.Controller) warp.Sender {
|
|
return &testSender{}
|
|
},
|
|
tx: &txs.Tx{Unsigned: &txs.BaseTx{}},
|
|
expectedErr: mempool.ErrDuplicateTx,
|
|
},
|
|
{
|
|
name: "transaction marked as dropped in mempool",
|
|
mempool: func() *pmempool.Mempool {
|
|
mempool, err := pmempool.New("", metric.NewRegistry())
|
|
require.NoError(t, err)
|
|
mempool.MarkDropped(ids.Empty, errTest)
|
|
return mempool
|
|
}(),
|
|
appSenderFunc: func(ctrl *gomock.Controller) warp.Sender {
|
|
// Shouldn't gossip the tx
|
|
return &testSender{}
|
|
},
|
|
tx: &txs.Tx{Unsigned: &txs.BaseTx{}},
|
|
expectedErr: errTest,
|
|
},
|
|
{
|
|
name: "tx dropped",
|
|
mempool: func() *pmempool.Mempool {
|
|
mempool, err := pmempool.New("", metric.NewRegistry())
|
|
require.NoError(t, err)
|
|
return mempool
|
|
}(),
|
|
txVerifier: testTxVerifier{err: errTest},
|
|
appSenderFunc: func(ctrl *gomock.Controller) warp.Sender {
|
|
// Shouldn't gossip the tx
|
|
return &testSender{}
|
|
},
|
|
tx: &txs.Tx{Unsigned: &txs.BaseTx{}},
|
|
expectedErr: errTest,
|
|
},
|
|
{
|
|
name: "tx too big",
|
|
mempool: func() *pmempool.Mempool {
|
|
mempool, err := pmempool.New("", metric.NewRegistry())
|
|
require.NoError(t, err)
|
|
return mempool
|
|
}(),
|
|
appSenderFunc: func(ctrl *gomock.Controller) warp.Sender {
|
|
// Shouldn't gossip the tx
|
|
return &testSender{}
|
|
},
|
|
tx: func() *txs.Tx {
|
|
tx := &txs.Tx{Unsigned: &txs.BaseTx{}}
|
|
bytes := make([]byte, mempool.MaxTxSize+1)
|
|
tx.SetBytes(bytes, bytes)
|
|
return tx
|
|
}(),
|
|
expectedErr: mempool.ErrTxTooLarge,
|
|
},
|
|
{
|
|
name: "tx conflicts",
|
|
mempool: func() *pmempool.Mempool {
|
|
mempool, err := pmempool.New("", metric.NewRegistry())
|
|
require.NoError(t, err)
|
|
|
|
tx := &txs.Tx{
|
|
Unsigned: &txs.BaseTx{
|
|
BaseTx: lux.BaseTx{
|
|
Ins: []*lux.TransferableInput{
|
|
{
|
|
UTXOID: lux.UTXOID{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
require.NoError(t, mempool.Add(tx))
|
|
return mempool
|
|
}(),
|
|
appSenderFunc: func(ctrl *gomock.Controller) warp.Sender {
|
|
// Shouldn't gossip the tx
|
|
return &testSender{}
|
|
},
|
|
tx: func() *txs.Tx {
|
|
tx := &txs.Tx{
|
|
Unsigned: &txs.BaseTx{
|
|
BaseTx: lux.BaseTx{
|
|
Ins: []*lux.TransferableInput{
|
|
{
|
|
UTXOID: lux.UTXOID{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TxID: ids.ID{1},
|
|
}
|
|
return tx
|
|
}(),
|
|
expectedErr: mempool.ErrConflictsWithOtherTx,
|
|
},
|
|
{
|
|
name: "mempool full",
|
|
mempool: func() *pmempool.Mempool {
|
|
m, err := pmempool.New("", metric.NewRegistry())
|
|
require.NoError(t, err)
|
|
|
|
// Fill the mempool to capacity (64 MiB / 2 MiB per tx = 32 txs)
|
|
for i := 0; i < 32; i++ {
|
|
tx := &txs.Tx{Unsigned: &txs.BaseTx{}}
|
|
bytes := make([]byte, mempool.MaxTxSize)
|
|
tx.SetBytes(bytes, bytes)
|
|
tx.TxID = ids.GenerateTestID()
|
|
require.NoError(t, m.Add(tx))
|
|
}
|
|
|
|
return m
|
|
}(),
|
|
appSenderFunc: func(ctrl *gomock.Controller) warp.Sender {
|
|
// Shouldn't gossip the tx
|
|
return &testSender{}
|
|
},
|
|
tx: func() *txs.Tx {
|
|
tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: lux.BaseTx{}}}
|
|
tx.SetBytes([]byte{1, 2, 3}, []byte{1, 2, 3})
|
|
return tx
|
|
}(),
|
|
expectedErr: mempool.ErrMempoolFull,
|
|
},
|
|
{
|
|
name: "happy path",
|
|
mempool: func() *pmempool.Mempool {
|
|
mempool, err := pmempool.New("", metric.NewRegistry())
|
|
require.NoError(t, err)
|
|
return mempool
|
|
}(),
|
|
appSenderFunc: func(ctrl *gomock.Controller) warp.Sender {
|
|
// testSender tracks if SendGossip was called
|
|
return &testSender{}
|
|
},
|
|
tx: &txs.Tx{Unsigned: &txs.BaseTx{}},
|
|
expectedErr: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
ctrl := gomock.NewController(t)
|
|
|
|
rt := consensustest.Runtime(t, ids.Empty)
|
|
// Extract values directly from Runtime
|
|
nodeID := rt.NodeID
|
|
netID := rt.ChainID
|
|
// Use a simple test logger for now
|
|
logger := log.NoLog{}
|
|
// Create a mock validator state that returns sensible defaults
|
|
validatorState := &mockValidatorState{
|
|
height: 100,
|
|
validators: map[ids.NodeID]*validators.GetValidatorOutput{
|
|
nodeID: {
|
|
NodeID: nodeID,
|
|
PublicKey: nil,
|
|
Weight: 100,
|
|
},
|
|
},
|
|
}
|
|
n, err := New(
|
|
logger,
|
|
nodeID,
|
|
netID,
|
|
validatorState,
|
|
tt.txVerifier,
|
|
tt.mempool,
|
|
false,
|
|
tt.appSenderFunc(ctrl),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
metric.NewRegistry(),
|
|
testConfig,
|
|
)
|
|
require.NoError(err)
|
|
|
|
err = n.IssueTxFromRPC(tt.tx)
|
|
require.ErrorIs(err, tt.expectedErr)
|
|
|
|
require.NoError(n.txPushGossiper.Gossip(context.Background()))
|
|
})
|
|
}
|
|
}
|