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 }