github.com/MetalBlockchain/metalgo@v1.11.9/vms/txs/mempool/mempool_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package mempool
     5  
     6  import (
     7  	"errors"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/MetalBlockchain/metalgo/ids"
    13  	"github.com/MetalBlockchain/metalgo/utils/set"
    14  )
    15  
    16  var _ Tx = (*dummyTx)(nil)
    17  
    18  type dummyTx struct {
    19  	size     int
    20  	id       ids.ID
    21  	inputIDs []ids.ID
    22  }
    23  
    24  func (tx *dummyTx) Size() int {
    25  	return tx.size
    26  }
    27  
    28  func (tx *dummyTx) ID() ids.ID {
    29  	return tx.id
    30  }
    31  
    32  func (tx *dummyTx) InputIDs() set.Set[ids.ID] {
    33  	return set.Of(tx.inputIDs...)
    34  }
    35  
    36  type noMetrics struct{}
    37  
    38  func (*noMetrics) Update(int, int) {}
    39  
    40  func newMempool() *mempool[*dummyTx] {
    41  	return New[*dummyTx](&noMetrics{})
    42  }
    43  
    44  func TestAdd(t *testing.T) {
    45  	tx0 := newTx(0, 32)
    46  
    47  	tests := []struct {
    48  		name       string
    49  		initialTxs []*dummyTx
    50  		tx         *dummyTx
    51  		err        error
    52  		dropReason error
    53  	}{
    54  		{
    55  			name:       "successfully add tx",
    56  			initialTxs: nil,
    57  			tx:         tx0,
    58  			err:        nil,
    59  			dropReason: nil,
    60  		},
    61  		{
    62  			name:       "attempt adding duplicate tx",
    63  			initialTxs: []*dummyTx{tx0},
    64  			tx:         tx0,
    65  			err:        ErrDuplicateTx,
    66  			dropReason: nil,
    67  		},
    68  		{
    69  			name:       "attempt adding too large tx",
    70  			initialTxs: nil,
    71  			tx:         newTx(0, MaxTxSize+1),
    72  			err:        ErrTxTooLarge,
    73  			dropReason: ErrTxTooLarge,
    74  		},
    75  		{
    76  			name:       "attempt adding tx when full",
    77  			initialTxs: newTxs(maxMempoolSize/MaxTxSize, MaxTxSize),
    78  			tx:         newTx(maxMempoolSize/MaxTxSize, MaxTxSize),
    79  			err:        ErrMempoolFull,
    80  			dropReason: nil,
    81  		},
    82  		{
    83  			name:       "attempt adding conflicting tx",
    84  			initialTxs: []*dummyTx{tx0},
    85  			tx:         newTx(0, 32),
    86  			err:        ErrConflictsWithOtherTx,
    87  			dropReason: ErrConflictsWithOtherTx,
    88  		},
    89  	}
    90  	for _, test := range tests {
    91  		t.Run(test.name, func(t *testing.T) {
    92  			require := require.New(t)
    93  
    94  			mempool := newMempool()
    95  
    96  			for _, tx := range test.initialTxs {
    97  				require.NoError(mempool.Add(tx))
    98  			}
    99  
   100  			err := mempool.Add(test.tx)
   101  			require.ErrorIs(err, test.err)
   102  
   103  			txID := test.tx.ID()
   104  
   105  			if err != nil {
   106  				mempool.MarkDropped(txID, err)
   107  			}
   108  
   109  			err = mempool.GetDropReason(txID)
   110  			require.ErrorIs(err, test.dropReason)
   111  		})
   112  	}
   113  }
   114  
   115  func TestGet(t *testing.T) {
   116  	require := require.New(t)
   117  
   118  	mempool := newMempool()
   119  
   120  	tx := newTx(0, 32)
   121  	txID := tx.ID()
   122  
   123  	_, exists := mempool.Get(txID)
   124  	require.False(exists)
   125  
   126  	require.NoError(mempool.Add(tx))
   127  
   128  	returned, exists := mempool.Get(txID)
   129  	require.True(exists)
   130  	require.Equal(tx, returned)
   131  
   132  	mempool.Remove(tx)
   133  
   134  	_, exists = mempool.Get(txID)
   135  	require.False(exists)
   136  }
   137  
   138  func TestPeek(t *testing.T) {
   139  	require := require.New(t)
   140  
   141  	mempool := newMempool()
   142  
   143  	_, exists := mempool.Peek()
   144  	require.False(exists)
   145  
   146  	tx0 := newTx(0, 32)
   147  	tx1 := newTx(1, 32)
   148  
   149  	require.NoError(mempool.Add(tx0))
   150  	require.NoError(mempool.Add(tx1))
   151  
   152  	tx, exists := mempool.Peek()
   153  	require.True(exists)
   154  	require.Equal(tx, tx0)
   155  
   156  	mempool.Remove(tx0)
   157  
   158  	tx, exists = mempool.Peek()
   159  	require.True(exists)
   160  	require.Equal(tx, tx1)
   161  
   162  	mempool.Remove(tx0)
   163  
   164  	tx, exists = mempool.Peek()
   165  	require.True(exists)
   166  	require.Equal(tx, tx1)
   167  
   168  	mempool.Remove(tx1)
   169  
   170  	_, exists = mempool.Peek()
   171  	require.False(exists)
   172  }
   173  
   174  func TestRemoveConflict(t *testing.T) {
   175  	require := require.New(t)
   176  
   177  	mempool := newMempool()
   178  
   179  	tx := newTx(0, 32)
   180  	txConflict := newTx(0, 32)
   181  
   182  	require.NoError(mempool.Add(tx))
   183  
   184  	returnedTx, exists := mempool.Peek()
   185  	require.True(exists)
   186  	require.Equal(returnedTx, tx)
   187  
   188  	mempool.Remove(txConflict)
   189  
   190  	_, exists = mempool.Peek()
   191  	require.False(exists)
   192  }
   193  
   194  func TestIterate(t *testing.T) {
   195  	require := require.New(t)
   196  
   197  	mempool := newMempool()
   198  
   199  	var (
   200  		iteratedTxs []*dummyTx
   201  		maxLen      = 2
   202  	)
   203  	addTxs := func(tx *dummyTx) bool {
   204  		iteratedTxs = append(iteratedTxs, tx)
   205  		return len(iteratedTxs) < maxLen
   206  	}
   207  	mempool.Iterate(addTxs)
   208  	require.Empty(iteratedTxs)
   209  
   210  	tx0 := newTx(0, 32)
   211  	require.NoError(mempool.Add(tx0))
   212  
   213  	mempool.Iterate(addTxs)
   214  	require.Equal([]*dummyTx{tx0}, iteratedTxs)
   215  
   216  	tx1 := newTx(1, 32)
   217  	require.NoError(mempool.Add(tx1))
   218  
   219  	iteratedTxs = nil
   220  	mempool.Iterate(addTxs)
   221  	require.Equal([]*dummyTx{tx0, tx1}, iteratedTxs)
   222  
   223  	tx2 := newTx(2, 32)
   224  	require.NoError(mempool.Add(tx2))
   225  
   226  	iteratedTxs = nil
   227  	mempool.Iterate(addTxs)
   228  	require.Equal([]*dummyTx{tx0, tx1}, iteratedTxs)
   229  
   230  	mempool.Remove(tx0, tx2)
   231  
   232  	iteratedTxs = nil
   233  	mempool.Iterate(addTxs)
   234  	require.Equal([]*dummyTx{tx1}, iteratedTxs)
   235  }
   236  
   237  func TestDropped(t *testing.T) {
   238  	require := require.New(t)
   239  
   240  	mempool := newMempool()
   241  
   242  	tx := newTx(0, 32)
   243  	txID := tx.ID()
   244  	testErr := errors.New("test")
   245  
   246  	mempool.MarkDropped(txID, testErr)
   247  
   248  	err := mempool.GetDropReason(txID)
   249  	require.ErrorIs(err, testErr)
   250  
   251  	require.NoError(mempool.Add(tx))
   252  	require.NoError(mempool.GetDropReason(txID))
   253  
   254  	mempool.MarkDropped(txID, testErr)
   255  	require.NoError(mempool.GetDropReason(txID))
   256  }
   257  
   258  func newTxs(num int, size int) []*dummyTx {
   259  	txs := make([]*dummyTx, num)
   260  	for i := range txs {
   261  		txs[i] = newTx(uint64(i), size)
   262  	}
   263  	return txs
   264  }
   265  
   266  func newTx(index uint64, size int) *dummyTx {
   267  	return &dummyTx{
   268  		size:     size,
   269  		id:       ids.GenerateTestID(),
   270  		inputIDs: []ids.ID{ids.Empty.Prefix(index)},
   271  	}
   272  }
   273  
   274  // shows that valid tx is not added to mempool if this would exceed its maximum
   275  // size
   276  func TestBlockBuilderMaxMempoolSizeHandling(t *testing.T) {
   277  	require := require.New(t)
   278  
   279  	mpool := newMempool()
   280  
   281  	tx := newTx(0, 32)
   282  
   283  	// shortcut to simulated almost filled mempool
   284  	mpool.bytesAvailable = tx.Size() - 1
   285  
   286  	err := mpool.Add(tx)
   287  	require.ErrorIs(err, ErrMempoolFull)
   288  
   289  	// tx should not be marked as dropped if the mempool is full
   290  	txID := tx.ID()
   291  	mpool.MarkDropped(txID, err)
   292  	require.NoError(mpool.GetDropReason(txID))
   293  
   294  	// shortcut to simulated almost filled mempool
   295  	mpool.bytesAvailable = tx.Size()
   296  
   297  	err = mpool.Add(tx)
   298  	require.NoError(err, "should have added tx to mempool")
   299  }