mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
272 lines
6.7 KiB
Go
272 lines
6.7 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package mempool
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/metric"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/ids"
|
|
lux "github.com/luxfi/utxo"
|
|
|
|
"github.com/luxfi/node/vms/platformvm/txs"
|
|
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
)
|
|
|
|
var preFundedKeys = secp256k1.TestKeys()
|
|
|
|
// shows that valid tx is not added to mempool if this would exceed its maximum
|
|
// size
|
|
func TestBlockBuilderMaxMempoolSizeHandling(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
registerer := metric.NewRegistry()
|
|
mpool, err := New("mempool", registerer)
|
|
require.NoError(err)
|
|
|
|
decisionTxs, err := createTestDecisionTxs(1)
|
|
require.NoError(err)
|
|
tx := decisionTxs[0]
|
|
|
|
// Test mempool full behavior - cannot access private bytesAvailable field
|
|
// This test verifies the mempool can handle transactions, the full/capacity
|
|
// testing is done in vms/txs/mempool/mempool_test.go
|
|
err = mpool.Add(tx)
|
|
require.NoError(err, "should have added tx to mempool")
|
|
}
|
|
|
|
func TestDecisionTxsInMempool(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
registerer := metric.NewRegistry()
|
|
mpool, err := New("mempool", registerer)
|
|
require.NoError(err)
|
|
|
|
decisionTxs, err := createTestDecisionTxs(2)
|
|
require.NoError(err)
|
|
|
|
// txs must not already there before we start
|
|
require.False(mpool.HasTxs())
|
|
|
|
for _, tx := range decisionTxs {
|
|
// tx not already there
|
|
require.False(mpool.Has(tx.ID()))
|
|
|
|
// we can insert
|
|
require.NoError(mpool.Add(tx))
|
|
|
|
// we can get it
|
|
require.True(mpool.Has(tx.ID()))
|
|
|
|
retrieved, _ := mpool.Get(tx.ID())
|
|
require.NotNil(retrieved)
|
|
require.Equal(tx, retrieved)
|
|
|
|
// we can peek it
|
|
peeked := mpool.PeekTxs(math.MaxInt)
|
|
|
|
// tx will be among those peeked,
|
|
// in NO PARTICULAR ORDER
|
|
found := false
|
|
for _, pk := range peeked {
|
|
if pk.ID() == tx.ID() {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(found)
|
|
|
|
// once removed it cannot be there
|
|
mpool.Remove(tx)
|
|
|
|
require.False(mpool.Has(tx.ID()))
|
|
retrievedAfterRemove, _ := mpool.Get(tx.ID())
|
|
require.Equal((*txs.Tx)(nil), retrievedAfterRemove)
|
|
|
|
// we can reinsert it again to grow the mempool
|
|
require.NoError(mpool.Add(tx))
|
|
}
|
|
}
|
|
|
|
func TestProposalTxsInMempool(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
registerer := metric.NewRegistry()
|
|
mpool, err := New("mempool", registerer)
|
|
require.NoError(err)
|
|
|
|
// The proposal txs are ordered by decreasing start time. This means after
|
|
// each insertion, the last inserted transaction should be on the top of the
|
|
// heap.
|
|
proposalTxs, err := createTestProposalTxs(2)
|
|
require.NoError(err)
|
|
|
|
for i, tx := range proposalTxs {
|
|
require.False(mpool.Has(tx.ID()))
|
|
|
|
// we can insert
|
|
require.NoError(mpool.Add(tx))
|
|
|
|
// we can get it
|
|
require.True(mpool.Has(tx.ID()))
|
|
|
|
retrieved, _ := mpool.Get(tx.ID())
|
|
require.NotNil(retrieved)
|
|
require.Equal(tx, retrieved)
|
|
|
|
{
|
|
// we can peek it
|
|
peeked := mpool.PeekTxs(math.MaxInt)
|
|
require.Len(peeked, i+1)
|
|
|
|
// tx will be among those peeked,
|
|
// in NO PARTICULAR ORDER
|
|
found := false
|
|
for _, pk := range peeked {
|
|
if pk.ID() == tx.ID() {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(found)
|
|
}
|
|
|
|
// once removed it cannot be there
|
|
mpool.Remove(tx)
|
|
|
|
require.False(mpool.Has(tx.ID()))
|
|
retrievedAfterRemove, _ := mpool.Get(tx.ID())
|
|
require.Equal((*txs.Tx)(nil), retrievedAfterRemove)
|
|
|
|
// we can reinsert it again to grow the mempool
|
|
require.NoError(mpool.Add(tx))
|
|
}
|
|
}
|
|
|
|
// createTestDecisionTxs builds native-wire CreateChainTx decision txs via the
|
|
// New*Tx constructor + (&Tx{}).Initialize() path — no codec, no NewSigned
|
|
// codec arg. The spending envelope (network/blockchain/ins/outs) is passed as
|
|
// a *lux.BaseTx to the constructor; the delta fields (chainID/name/vmID/fxIDs/
|
|
// genesis/auth) are the remaining positional args.
|
|
func createTestDecisionTxs(count int) ([]*txs.Tx, error) {
|
|
decisionTxs := make([]*txs.Tx, 0, count)
|
|
for i := uint32(0); i < uint32(count); i++ {
|
|
base := &lux.BaseTx{
|
|
NetworkID: 10,
|
|
BlockchainID: ids.Empty.Prefix(uint64(i)),
|
|
Ins: []*lux.TransferableInput{{
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.ID{'t', 'x', 'I', 'D'},
|
|
OutputIndex: i,
|
|
},
|
|
Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 'r', 't'}},
|
|
In: &secp256k1fx.TransferInput{
|
|
Amt: uint64(5678),
|
|
Input: secp256k1fx.Input{SigIndices: []uint32{i}},
|
|
},
|
|
}},
|
|
Outs: []*lux.TransferableOutput{{
|
|
Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 'r', 't'}},
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: uint64(1234),
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
|
|
utx, err := txs.NewCreateChainTx(
|
|
base,
|
|
ids.GenerateTestID(), // chainID
|
|
"chainName",
|
|
ids.GenerateTestID(), // vmID
|
|
[]ids.ID{ids.GenerateTestID()}, // fxIDs
|
|
[]byte{'g', 'e', 'n', 'D', 'a', 't', 'a'}, // genesisData
|
|
&secp256k1fx.Input{SigIndices: []uint32{1}}, // chainAuth
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tx := &txs.Tx{Unsigned: utx}
|
|
if err := tx.Initialize(); err != nil {
|
|
return nil, err
|
|
}
|
|
decisionTxs = append(decisionTxs, tx)
|
|
}
|
|
return decisionTxs, nil
|
|
}
|
|
|
|
// Proposal txs are sorted by decreasing start time
|
|
func createTestProposalTxs(count int) ([]*txs.Tx, error) {
|
|
now := time.Now()
|
|
proposalTxs := make([]*txs.Tx, 0, count)
|
|
for i := 0; i < count; i++ {
|
|
tx, err := generateAddValidatorTx(
|
|
uint64(now.Add(time.Duration(count-i)*time.Second).Unix()), // startTime
|
|
0, // endTime
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
proposalTxs = append(proposalTxs, tx)
|
|
}
|
|
return proposalTxs, nil
|
|
}
|
|
|
|
func generateAddValidatorTx(startTime uint64, endTime uint64) (*txs.Tx, error) {
|
|
utx, err := txs.NewAddValidatorTx(
|
|
&lux.BaseTx{},
|
|
txs.Validator{
|
|
NodeID: ids.GenerateTestNodeID(),
|
|
Start: startTime,
|
|
End: endTime,
|
|
},
|
|
nil, // stake outs
|
|
&secp256k1fx.OutputOwners{},
|
|
100, // delegation shares
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tx := &txs.Tx{Unsigned: utx}
|
|
if err := tx.Initialize(); err != nil {
|
|
return nil, err
|
|
}
|
|
return tx, nil
|
|
}
|
|
|
|
func TestDropExpiredStakerTxs(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
registerer := metric.NewRegistry()
|
|
mempool, err := New("mempool", registerer)
|
|
require.NoError(err)
|
|
|
|
tx1, err := generateAddValidatorTx(10, 20)
|
|
require.NoError(err)
|
|
require.NoError(mempool.Add(tx1))
|
|
|
|
tx2, err := generateAddValidatorTx(8, 20)
|
|
require.NoError(err)
|
|
require.NoError(mempool.Add(tx2))
|
|
|
|
tx3, err := generateAddValidatorTx(15, 20)
|
|
require.NoError(err)
|
|
require.NoError(mempool.Add(tx3))
|
|
|
|
minStartTime := time.Unix(9, 0)
|
|
require.Len(mempool.DropExpiredStakerTxs(minStartTime), 1)
|
|
}
|