github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/mempool/mem_pool_test.go (about) 1 package mempool 2 3 import ( 4 "fmt" 5 "math/big" 6 "sort" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/holiman/uint256" 12 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 13 "github.com/nspcc-dev/neo-go/pkg/network/payload" 14 "github.com/nspcc-dev/neo-go/pkg/util" 15 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 type FeerStub struct { 21 feePerByte int64 22 p2pSigExt bool 23 blockHeight uint32 24 balance int64 25 } 26 27 func (fs *FeerStub) GetBaseExecFee() int64 { 28 return 30 29 } 30 31 func (fs *FeerStub) FeePerByte() int64 { 32 return fs.feePerByte 33 } 34 35 func (fs *FeerStub) BlockHeight() uint32 { 36 return fs.blockHeight 37 } 38 39 func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int { 40 return big.NewInt(fs.balance) 41 } 42 43 func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) { 44 mp := New(10, 0, false, nil) 45 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 46 tx.Nonce = 0 47 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 48 _, ok := mp.TryGetValue(tx.Hash()) 49 require.Equal(t, false, ok) 50 require.NoError(t, mp.Add(tx, fs)) 51 // Re-adding should fail. 52 require.Error(t, mp.Add(tx, fs)) 53 tx2, ok := mp.TryGetValue(tx.Hash()) 54 require.Equal(t, true, ok) 55 require.Equal(t, tx, tx2) 56 mp.Remove(tx.Hash(), fs) 57 _, ok = mp.TryGetValue(tx.Hash()) 58 require.Equal(t, false, ok) 59 // Make sure nothing left in the mempool after removal. 60 assert.Equal(t, 0, len(mp.verifiedMap)) 61 assert.Equal(t, 0, len(mp.verifiedTxes)) 62 } 63 64 func TestMemPoolRemoveStale(t *testing.T) { 65 mp := New(5, 0, false, nil) 66 txs := make([]*transaction.Transaction, 5) 67 for i := range txs { 68 txs[i] = transaction.New([]byte{byte(opcode.PUSH1)}, 0) 69 txs[i].Nonce = uint32(i) 70 txs[i].Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 71 require.NoError(t, mp.Add(txs[i], &FeerStub{blockHeight: uint32(i)})) 72 } 73 74 staleTxs := make(chan *transaction.Transaction, 5) 75 f := func(tx *transaction.Transaction, _ any) { 76 staleTxs <- tx 77 } 78 mp.SetResendThreshold(5, f) 79 80 isValid := func(tx *transaction.Transaction) bool { 81 return tx.Nonce%2 == 0 82 } 83 84 mp.RemoveStale(isValid, &FeerStub{blockHeight: 5}) // 0 + 5 85 require.Eventually(t, func() bool { return len(staleTxs) == 1 }, time.Second, time.Millisecond*100) 86 require.Equal(t, txs[0], <-staleTxs) 87 88 mp.RemoveStale(isValid, &FeerStub{blockHeight: 7}) // 2 + 5 89 require.Eventually(t, func() bool { return len(staleTxs) == 1 }, time.Second, time.Millisecond*100) 90 require.Equal(t, txs[2], <-staleTxs) 91 92 mp.RemoveStale(isValid, &FeerStub{blockHeight: 10}) // 0 + 2 * 5 93 require.Eventually(t, func() bool { return len(staleTxs) == 1 }, time.Second, time.Millisecond*100) 94 require.Equal(t, txs[0], <-staleTxs) 95 96 mp.RemoveStale(isValid, &FeerStub{blockHeight: 15}) // 0 + 3 * 5 97 98 // tx[2] should appear, so it is also checked that tx[0] wasn't sent on height 15. 99 mp.RemoveStale(isValid, &FeerStub{blockHeight: 22}) // 2 + 4 * 5 100 require.Eventually(t, func() bool { return len(staleTxs) == 1 }, time.Second, time.Millisecond*100) 101 require.Equal(t, txs[2], <-staleTxs) 102 103 // panic if something is sent after this. 104 close(staleTxs) 105 require.Len(t, staleTxs, 0) 106 } 107 108 func TestMemPoolAddRemove(t *testing.T) { 109 var fs = &FeerStub{} 110 testMemPoolAddRemoveWithFeer(t, fs) 111 } 112 113 func TestOverCapacity(t *testing.T) { 114 var fs = &FeerStub{balance: 10000000} 115 const mempoolSize = 10 116 mp := New(mempoolSize, 0, false, nil) 117 118 for i := 0; i < mempoolSize; i++ { 119 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 120 tx.Nonce = uint32(i) 121 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 122 require.NoError(t, mp.Add(tx, fs)) 123 } 124 txcnt := uint32(mempoolSize) 125 require.Equal(t, mempoolSize, mp.Count()) 126 require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 127 128 bigScript := make([]byte, 64) 129 bigScript[0] = byte(opcode.PUSH1) 130 bigScript[1] = byte(opcode.RET) 131 // Fees are also prioritized. 132 for i := 0; i < mempoolSize; i++ { 133 tx := transaction.New(bigScript, 0) 134 tx.NetworkFee = 10000 135 tx.Nonce = txcnt 136 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 137 txcnt++ 138 // size is ~90, networkFee is 10000 => feePerByte is 119 139 require.NoError(t, mp.Add(tx, fs)) 140 require.Equal(t, mempoolSize, mp.Count()) 141 require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 142 } 143 // Less prioritized txes are not allowed anymore. 144 tx := transaction.New(bigScript, 0) 145 tx.NetworkFee = 100 146 tx.Nonce = txcnt 147 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 148 txcnt++ 149 require.Error(t, mp.Add(tx, fs)) 150 require.Equal(t, mempoolSize, mp.Count()) 151 require.Equal(t, mempoolSize, len(mp.verifiedMap)) 152 require.Equal(t, mempoolSize, len(mp.verifiedTxes)) 153 require.False(t, mp.containsKey(tx.Hash())) 154 require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 155 156 // Low net fee, but higher per-byte fee is still a better combination. 157 tx = transaction.New([]byte{byte(opcode.PUSH1)}, 0) 158 tx.Nonce = txcnt 159 tx.NetworkFee = 7000 160 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 161 txcnt++ 162 // size is ~51 (small script), networkFee is 7000 (<10000) 163 // => feePerByte is 137 (>119) 164 require.NoError(t, mp.Add(tx, fs)) 165 require.Equal(t, mempoolSize, mp.Count()) 166 require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 167 168 // High priority always wins over low priority. 169 for i := 0; i < mempoolSize; i++ { 170 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 171 tx.NetworkFee = 8000 172 tx.Nonce = txcnt 173 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 174 txcnt++ 175 require.NoError(t, mp.Add(tx, fs)) 176 require.Equal(t, mempoolSize, mp.Count()) 177 require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 178 } 179 // Good luck with low priority now. 180 tx = transaction.New([]byte{byte(opcode.PUSH1)}, 0) 181 tx.Nonce = txcnt 182 tx.NetworkFee = 7000 183 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 184 require.Error(t, mp.Add(tx, fs)) 185 require.Equal(t, mempoolSize, mp.Count()) 186 require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 187 } 188 189 func TestGetVerified(t *testing.T) { 190 var fs = &FeerStub{} 191 const mempoolSize = 10 192 mp := New(mempoolSize, 0, false, nil) 193 194 txes := make([]*transaction.Transaction, 0, mempoolSize) 195 for i := 0; i < mempoolSize; i++ { 196 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 197 tx.Nonce = uint32(i) 198 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 199 txes = append(txes, tx) 200 require.NoError(t, mp.Add(tx, fs)) 201 } 202 require.Equal(t, mempoolSize, mp.Count()) 203 verTxes := mp.GetVerifiedTransactions() 204 require.Equal(t, mempoolSize, len(verTxes)) 205 require.ElementsMatch(t, txes, verTxes) 206 for _, tx := range txes { 207 mp.Remove(tx.Hash(), fs) 208 } 209 verTxes = mp.GetVerifiedTransactions() 210 require.Equal(t, 0, len(verTxes)) 211 } 212 213 func TestRemoveStale(t *testing.T) { 214 var fs = &FeerStub{} 215 const mempoolSize = 10 216 mp := New(mempoolSize, 0, false, nil) 217 218 txes1 := make([]*transaction.Transaction, 0, mempoolSize/2) 219 txes2 := make([]*transaction.Transaction, 0, mempoolSize/2) 220 for i := 0; i < mempoolSize; i++ { 221 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 222 tx.Nonce = uint32(i) 223 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 224 if i%2 == 0 { 225 txes1 = append(txes1, tx) 226 } else { 227 txes2 = append(txes2, tx) 228 } 229 require.NoError(t, mp.Add(tx, fs)) 230 } 231 require.Equal(t, mempoolSize, mp.Count()) 232 mp.RemoveStale(func(t *transaction.Transaction) bool { 233 for _, tx := range txes2 { 234 if tx == t { 235 return true 236 } 237 } 238 return false 239 }, &FeerStub{}) 240 require.Equal(t, mempoolSize/2, mp.Count()) 241 verTxes := mp.GetVerifiedTransactions() 242 for _, txf := range verTxes { 243 require.NotContains(t, txes1, txf) 244 require.Contains(t, txes2, txf) 245 } 246 } 247 248 func TestMemPoolFees(t *testing.T) { 249 mp := New(10, 0, false, nil) 250 fs := &FeerStub{balance: 10000000} 251 sender0 := util.Uint160{1, 2, 3} 252 tx0 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 253 tx0.NetworkFee = fs.balance + 1 254 tx0.Signers = []transaction.Signer{{Account: sender0}} 255 // insufficient funds to add transaction, and balance shouldn't be stored 256 require.Equal(t, false, mp.Verify(tx0, fs)) 257 require.Error(t, mp.Add(tx0, fs)) 258 require.Equal(t, 0, len(mp.fees)) 259 260 balancePart := new(big.Int).Div(big.NewInt(fs.balance), big.NewInt(4)) 261 // no problems with adding another transaction with lower fee 262 tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 263 tx1.NetworkFee = balancePart.Int64() 264 tx1.Signers = []transaction.Signer{{Account: sender0}} 265 require.NoError(t, mp.Add(tx1, fs)) 266 require.Equal(t, 1, len(mp.fees)) 267 require.Equal(t, utilityBalanceAndFees{ 268 balance: *uint256.NewInt(uint64(fs.balance)), 269 feeSum: *uint256.NewInt(uint64(tx1.NetworkFee)), 270 }, mp.fees[sender0]) 271 272 // balance shouldn't change after adding one more transaction 273 tx2 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 274 tx2.NetworkFee = new(big.Int).Sub(big.NewInt(fs.balance), balancePart).Int64() 275 tx2.Signers = []transaction.Signer{{Account: sender0}} 276 require.NoError(t, mp.Add(tx2, fs)) 277 require.Equal(t, 2, len(mp.verifiedTxes)) 278 require.Equal(t, 1, len(mp.fees)) 279 require.Equal(t, utilityBalanceAndFees{ 280 balance: *uint256.NewInt(uint64(fs.balance)), 281 feeSum: *uint256.NewInt(uint64(fs.balance)), 282 }, mp.fees[sender0]) 283 284 // can't add more transactions as we don't have enough GAS 285 tx3 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 286 tx3.NetworkFee = 1 287 tx3.Signers = []transaction.Signer{{Account: sender0}} 288 require.Equal(t, false, mp.Verify(tx3, fs)) 289 require.Error(t, mp.Add(tx3, fs)) 290 require.Equal(t, 1, len(mp.fees)) 291 require.Equal(t, utilityBalanceAndFees{ 292 balance: *uint256.NewInt(uint64(fs.balance)), 293 feeSum: *uint256.NewInt(uint64(fs.balance)), 294 }, mp.fees[sender0]) 295 296 // check whether sender's fee updates correctly 297 mp.RemoveStale(func(t *transaction.Transaction) bool { 298 return t == tx2 299 }, fs) 300 require.Equal(t, 1, len(mp.fees)) 301 require.Equal(t, utilityBalanceAndFees{ 302 balance: *uint256.NewInt(uint64(fs.balance)), 303 feeSum: *uint256.NewInt(uint64(tx2.NetworkFee)), 304 }, mp.fees[sender0]) 305 306 // there should be nothing left 307 mp.RemoveStale(func(t *transaction.Transaction) bool { 308 return t == tx3 309 }, fs) 310 require.Equal(t, 0, len(mp.fees)) 311 } 312 313 func TestMempoolItemsOrder(t *testing.T) { 314 sender0 := util.Uint160{1, 2, 3} 315 balance := big.NewInt(10000000) 316 317 tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 318 tx1.NetworkFee = new(big.Int).Div(balance, big.NewInt(8)).Int64() 319 tx1.Signers = []transaction.Signer{{Account: sender0}} 320 tx1.Attributes = []transaction.Attribute{{Type: transaction.HighPriority}} 321 item1 := item{txn: tx1} 322 323 tx2 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 324 tx2.NetworkFee = new(big.Int).Div(balance, big.NewInt(16)).Int64() 325 tx2.Signers = []transaction.Signer{{Account: sender0}} 326 tx2.Attributes = []transaction.Attribute{{Type: transaction.HighPriority}} 327 item2 := item{txn: tx2} 328 329 tx3 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 330 tx3.NetworkFee = new(big.Int).Div(balance, big.NewInt(2)).Int64() 331 tx3.Signers = []transaction.Signer{{Account: sender0}} 332 item3 := item{txn: tx3} 333 334 tx4 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 335 tx4.NetworkFee = new(big.Int).Div(balance, big.NewInt(4)).Int64() 336 tx4.Signers = []transaction.Signer{{Account: sender0}} 337 item4 := item{txn: tx4} 338 339 require.True(t, item1.CompareTo(item2) > 0) 340 require.True(t, item2.CompareTo(item1) < 0) 341 require.True(t, item1.CompareTo(item3) > 0) 342 require.True(t, item3.CompareTo(item1) < 0) 343 require.True(t, item1.CompareTo(item4) > 0) 344 require.True(t, item4.CompareTo(item1) < 0) 345 require.True(t, item2.CompareTo(item3) > 0) 346 require.True(t, item3.CompareTo(item2) < 0) 347 require.True(t, item2.CompareTo(item4) > 0) 348 require.True(t, item4.CompareTo(item2) < 0) 349 require.True(t, item3.CompareTo(item4) > 0) 350 require.True(t, item4.CompareTo(item3) < 0) 351 } 352 353 func TestMempoolAddRemoveOracleResponse(t *testing.T) { 354 mp := New(3, 0, false, nil) 355 nonce := uint32(0) 356 fs := &FeerStub{balance: 10000} 357 newTx := func(netFee int64, id uint64) *transaction.Transaction { 358 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 359 tx.NetworkFee = netFee 360 tx.Nonce = nonce 361 nonce++ 362 tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} 363 tx.Attributes = []transaction.Attribute{{ 364 Type: transaction.OracleResponseT, 365 Value: &transaction.OracleResponse{ID: id}, 366 }} 367 // sanity check 368 _, ok := mp.TryGetValue(tx.Hash()) 369 require.False(t, ok) 370 return tx 371 } 372 373 tx1 := newTx(10, 1) 374 require.NoError(t, mp.Add(tx1, fs)) 375 376 // smaller network fee 377 tx2 := newTx(5, 1) 378 err := mp.Add(tx2, fs) 379 require.ErrorIs(t, err, ErrOracleResponse) 380 381 // ok if old tx is removed 382 mp.Remove(tx1.Hash(), fs) 383 require.NoError(t, mp.Add(tx2, fs)) 384 385 // higher network fee 386 tx3 := newTx(6, 1) 387 require.NoError(t, mp.Add(tx3, fs)) 388 _, ok := mp.TryGetValue(tx2.Hash()) 389 require.False(t, ok) 390 _, ok = mp.TryGetValue(tx3.Hash()) 391 require.True(t, ok) 392 393 // another oracle response ID 394 tx4 := newTx(4, 2) 395 require.NoError(t, mp.Add(tx4, fs)) 396 397 mp.RemoveStale(func(tx *transaction.Transaction) bool { 398 return tx.Hash() != tx4.Hash() 399 }, fs) 400 401 // check that oracle id was removed. 402 tx5 := newTx(3, 2) 403 require.NoError(t, mp.Add(tx5, fs)) 404 405 // another oracle response ID with high net fee 406 tx6 := newTx(6, 3) 407 require.NoError(t, mp.Add(tx6, fs)) 408 // check respIds 409 for _, i := range []uint64{1, 2, 3} { 410 _, ok := mp.oracleResp[i] 411 require.True(t, ok) 412 } 413 // reach capacity, check that response ID is removed together with tx5 414 tx7 := newTx(6, 4) 415 require.NoError(t, mp.Add(tx7, fs)) 416 for _, i := range []uint64{1, 4, 3} { 417 _, ok := mp.oracleResp[i] 418 require.True(t, ok) 419 } 420 } 421 422 func TestMempoolAddRemoveConflicts(t *testing.T) { 423 var ( 424 capacity = 6 425 mp = New(capacity, 0, false, nil) 426 sender = transaction.Signer{Account: util.Uint160{1, 2, 3}} 427 maliciousSender = transaction.Signer{Account: util.Uint160{4, 5, 6}} 428 ) 429 430 var ( 431 fs = &FeerStub{p2pSigExt: true, balance: 100000} 432 nonce uint32 = 1 433 ) 434 getTx := func(netFee int64, sender transaction.Signer, hashes ...util.Uint256) *transaction.Transaction { 435 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) 436 tx.NetworkFee = netFee 437 tx.Nonce = nonce 438 nonce++ 439 tx.Signers = []transaction.Signer{sender} 440 tx.Attributes = make([]transaction.Attribute, len(hashes)) 441 for i, h := range hashes { 442 tx.Attributes[i] = transaction.Attribute{ 443 Type: transaction.ConflictsT, 444 Value: &transaction.Conflicts{ 445 Hash: h, 446 }, 447 } 448 } 449 _, ok := mp.TryGetValue(tx.Hash()) 450 require.Equal(t, false, ok) 451 return tx 452 } 453 getConflictsTx := func(netFee int64, hashes ...util.Uint256) *transaction.Transaction { 454 return getTx(netFee, sender, hashes...) 455 } 456 getMaliciousTx := func(netFee int64, hashes ...util.Uint256) *transaction.Transaction { 457 return getTx(netFee, maliciousSender, hashes...) 458 } 459 460 // tx1 in mempool and does not conflicts with anyone 461 smallNetFee := int64(3) 462 tx1 := getConflictsTx(smallNetFee) 463 require.NoError(t, mp.Add(tx1, fs)) 464 465 // tx2 conflicts with tx1 and has smaller netfee (Step 2, negative) 466 tx2 := getConflictsTx(smallNetFee-1, tx1.Hash()) 467 require.ErrorIs(t, mp.Add(tx2, fs), ErrConflictsAttribute) 468 469 // tx3 conflicts with mempooled tx1 and has larger netfee => tx1 should be replaced by tx3 (Step 2, positive) 470 tx3 := getConflictsTx(smallNetFee+1, tx1.Hash()) 471 require.NoError(t, mp.Add(tx3, fs)) 472 assert.Equal(t, 1, mp.Count()) 473 assert.Equal(t, 1, len(mp.conflicts)) 474 assert.Equal(t, []util.Uint256{tx3.Hash()}, mp.conflicts[tx1.Hash()]) 475 476 // tx1 still does not conflicts with anyone, but tx3 is mempooled, conflicts with tx1 477 // and has larger netfee => tx1 shouldn't be added again (Step 1, negative) 478 require.ErrorIs(t, mp.Add(tx1, fs), ErrConflictsAttribute) 479 480 // tx2 can now safely be added because conflicting tx1 is not in mempool => we 481 // cannot check that tx2 is signed by tx1.Sender 482 require.NoError(t, mp.Add(tx2, fs)) 483 assert.Equal(t, 1, len(mp.conflicts)) 484 assert.Equal(t, []util.Uint256{tx3.Hash(), tx2.Hash()}, mp.conflicts[tx1.Hash()]) 485 486 // mempooled tx4 conflicts with tx5, but tx4 has smaller netfee => tx4 should be replaced by tx5 (Step 1, positive) 487 tx5 := getConflictsTx(smallNetFee + 1) 488 tx4 := getConflictsTx(smallNetFee, tx5.Hash()) 489 require.NoError(t, mp.Add(tx4, fs)) // unverified 490 assert.Equal(t, 2, len(mp.conflicts)) 491 assert.Equal(t, []util.Uint256{tx4.Hash()}, mp.conflicts[tx5.Hash()]) 492 require.NoError(t, mp.Add(tx5, fs)) 493 // tx5 does not conflict with anyone 494 assert.Equal(t, 1, len(mp.conflicts)) 495 496 // multiple conflicts in attributes of single transaction 497 tx6 := getConflictsTx(smallNetFee) 498 tx7 := getConflictsTx(smallNetFee) 499 tx8 := getConflictsTx(smallNetFee) 500 // need small network fee later 501 tx9 := getConflictsTx(smallNetFee-2, tx6.Hash(), tx7.Hash(), tx8.Hash()) 502 require.NoError(t, mp.Add(tx9, fs)) 503 assert.Equal(t, 4, len(mp.conflicts)) 504 assert.Equal(t, []util.Uint256{tx9.Hash()}, mp.conflicts[tx6.Hash()]) 505 assert.Equal(t, []util.Uint256{tx9.Hash()}, mp.conflicts[tx7.Hash()]) 506 assert.Equal(t, []util.Uint256{tx9.Hash()}, mp.conflicts[tx8.Hash()]) 507 assert.Equal(t, []util.Uint256{tx3.Hash(), tx2.Hash()}, mp.conflicts[tx1.Hash()]) 508 509 // multiple conflicts in attributes of multiple transactions 510 tx10 := getConflictsTx(smallNetFee, tx6.Hash()) 511 tx11 := getConflictsTx(smallNetFee, tx6.Hash()) 512 require.NoError(t, mp.Add(tx10, fs)) // unverified, because tx6 is not in the pool 513 require.NoError(t, mp.Add(tx11, fs)) // unverified, because tx6 is not in the pool 514 assert.Equal(t, 4, len(mp.conflicts)) 515 assert.Equal(t, []util.Uint256{tx9.Hash(), tx10.Hash(), tx11.Hash()}, mp.conflicts[tx6.Hash()]) 516 assert.Equal(t, []util.Uint256{tx9.Hash()}, mp.conflicts[tx7.Hash()]) 517 assert.Equal(t, []util.Uint256{tx9.Hash()}, mp.conflicts[tx8.Hash()]) 518 assert.Equal(t, []util.Uint256{tx3.Hash(), tx2.Hash()}, mp.conflicts[tx1.Hash()]) 519 520 // reach capacity, remove less prioritised tx9 with its multiple conflicts 521 require.Equal(t, capacity, len(mp.verifiedTxes)) 522 tx12 := getConflictsTx(smallNetFee + 2) 523 require.NoError(t, mp.Add(tx12, fs)) 524 assert.Equal(t, 2, len(mp.conflicts)) 525 assert.Equal(t, []util.Uint256{tx10.Hash(), tx11.Hash()}, mp.conflicts[tx6.Hash()]) 526 assert.Equal(t, []util.Uint256{tx3.Hash(), tx2.Hash()}, mp.conflicts[tx1.Hash()]) 527 528 // manually remove tx11 with its single conflict 529 mp.Remove(tx11.Hash(), fs) 530 assert.Equal(t, 2, len(mp.conflicts)) 531 assert.Equal(t, []util.Uint256{tx10.Hash()}, mp.conflicts[tx6.Hash()]) 532 533 // manually remove last tx which conflicts with tx6 => mp.conflicts[tx6] should also be deleted 534 mp.Remove(tx10.Hash(), fs) 535 assert.Equal(t, 1, len(mp.conflicts)) 536 assert.Equal(t, []util.Uint256{tx3.Hash(), tx2.Hash()}, mp.conflicts[tx1.Hash()]) 537 538 // tx13 conflicts with tx2, but is not signed by tx2.Sender 539 tx13 := getMaliciousTx(smallNetFee, tx2.Hash()) 540 _, ok := mp.TryGetValue(tx13.Hash()) 541 require.Equal(t, false, ok) 542 require.ErrorIs(t, mp.Add(tx13, fs), ErrConflictsAttribute) 543 544 // tx15 conflicts with tx14, but added firstly and has the same network fee => tx14 must not be added. 545 tx14 := getConflictsTx(smallNetFee) 546 tx15 := getConflictsTx(smallNetFee, tx14.Hash()) 547 require.NoError(t, mp.Add(tx15, fs)) 548 err := mp.Add(tx14, fs) 549 require.Error(t, err) 550 551 require.True(t, strings.Contains(err.Error(), fmt.Sprintf("conflicting transactions have bigger or equal network fee: %d vs %d", smallNetFee, smallNetFee))) 552 553 check := func(t *testing.T, mainFee int64, fail bool) { 554 // Clear mempool. 555 mp.RemoveStale(func(t *transaction.Transaction) bool { 556 return false 557 }, fs) 558 559 // mempooled tx17, tx18, tx19 conflict with tx16 560 tx16 := getConflictsTx(mainFee) 561 tx17 := getConflictsTx(smallNetFee, tx16.Hash()) 562 tx18 := getConflictsTx(smallNetFee, tx16.Hash()) 563 tx19 := getMaliciousTx(smallNetFee, tx16.Hash()) // malicious, thus, doesn't take into account during fee evaluation 564 require.NoError(t, mp.Add(tx17, fs)) 565 require.NoError(t, mp.Add(tx18, fs)) 566 require.NoError(t, mp.Add(tx19, fs)) 567 if fail { 568 require.Error(t, mp.Add(tx16, fs)) 569 _, ok = mp.TryGetValue(tx17.Hash()) 570 require.True(t, ok) 571 _, ok = mp.TryGetValue(tx18.Hash()) 572 require.True(t, ok) 573 _, ok = mp.TryGetValue(tx19.Hash()) 574 require.True(t, ok) 575 } else { 576 require.NoError(t, mp.Add(tx16, fs)) 577 _, ok = mp.TryGetValue(tx17.Hash()) 578 require.False(t, ok) 579 _, ok = mp.TryGetValue(tx18.Hash()) 580 require.False(t, ok) 581 _, ok = mp.TryGetValue(tx19.Hash()) 582 require.False(t, ok) 583 } 584 } 585 check(t, smallNetFee*2, true) 586 check(t, smallNetFee*2+1, false) 587 588 check = func(t *testing.T, mainFee int64, fail bool) { 589 // Clear mempool. 590 mp.RemoveStale(func(t *transaction.Transaction) bool { 591 return false 592 }, fs) 593 594 // mempooled tx20, tx21, tx22 don't conflict with anyone, but tx23 conflicts with them 595 tx20 := getConflictsTx(smallNetFee) 596 tx21 := getConflictsTx(smallNetFee) 597 tx22 := getConflictsTx(smallNetFee) 598 tx23 := getConflictsTx(mainFee, tx20.Hash(), tx21.Hash(), tx22.Hash()) 599 require.NoError(t, mp.Add(tx20, fs)) 600 require.NoError(t, mp.Add(tx21, fs)) 601 require.NoError(t, mp.Add(tx22, fs)) 602 if fail { 603 require.Error(t, mp.Add(tx23, fs)) 604 _, ok = mp.TryGetData(tx20.Hash()) 605 require.True(t, ok) 606 _, ok = mp.TryGetData(tx21.Hash()) 607 require.True(t, ok) 608 _, ok = mp.TryGetData(tx22.Hash()) 609 require.True(t, ok) 610 } else { 611 require.NoError(t, mp.Add(tx23, fs)) 612 _, ok = mp.TryGetData(tx20.Hash()) 613 require.False(t, ok) 614 _, ok = mp.TryGetData(tx21.Hash()) 615 require.False(t, ok) 616 _, ok = mp.TryGetData(tx22.Hash()) 617 require.False(t, ok) 618 } 619 } 620 check(t, smallNetFee*3, true) 621 check(t, smallNetFee*3+1, false) 622 } 623 624 func TestMempoolAddWithDataGetData(t *testing.T) { 625 var ( 626 smallNetFee int64 = 3 627 nonce uint32 628 ) 629 fs := &FeerStub{ 630 feePerByte: 0, 631 p2pSigExt: true, 632 blockHeight: 5, 633 balance: 100, 634 } 635 mp := New(10, 1, false, nil) 636 637 // bad, insufficient deposit 638 r1 := &payload.P2PNotaryRequest{ 639 MainTransaction: mkTwoSignersTx(0, &nonce), 640 FallbackTransaction: mkTwoSignersTx(fs.balance+1, &nonce), 641 } 642 require.ErrorIs(t, mp.Add(r1.FallbackTransaction, fs, r1), ErrInsufficientFunds) 643 644 // good 645 r2 := &payload.P2PNotaryRequest{ 646 MainTransaction: mkTwoSignersTx(0, &nonce), 647 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 648 } 649 require.NoError(t, mp.Add(r2.FallbackTransaction, fs, r2)) 650 require.True(t, mp.ContainsKey(r2.FallbackTransaction.Hash())) 651 data, ok := mp.TryGetData(r2.FallbackTransaction.Hash()) 652 require.True(t, ok) 653 require.Equal(t, r2, data) 654 655 // bad, already in pool 656 require.ErrorIs(t, mp.Add(r2.FallbackTransaction, fs, r2), ErrDup) 657 658 // good, higher priority than r2. The resulting mp.verifiedTxes: [r3, r2] 659 r3 := &payload.P2PNotaryRequest{ 660 MainTransaction: mkTwoSignersTx(0, &nonce), 661 FallbackTransaction: mkTwoSignersTx(smallNetFee+1, &nonce), 662 } 663 require.NoError(t, mp.Add(r3.FallbackTransaction, fs, r3)) 664 require.True(t, mp.ContainsKey(r3.FallbackTransaction.Hash())) 665 data, ok = mp.TryGetData(r3.FallbackTransaction.Hash()) 666 require.True(t, ok) 667 require.Equal(t, r3, data) 668 669 // good, same priority as r2. The resulting mp.verifiedTxes: [r3, r2, r4] 670 r4 := &payload.P2PNotaryRequest{ 671 MainTransaction: mkTwoSignersTx(0, &nonce), 672 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 673 } 674 require.NoError(t, mp.Add(r4.FallbackTransaction, fs, r4)) 675 require.True(t, mp.ContainsKey(r4.FallbackTransaction.Hash())) 676 data, ok = mp.TryGetData(r4.FallbackTransaction.Hash()) 677 require.True(t, ok) 678 require.Equal(t, r4, data) 679 680 // good, same priority as r2. The resulting mp.verifiedTxes: [r3, r2, r4, r5] 681 r5 := &payload.P2PNotaryRequest{ 682 MainTransaction: mkTwoSignersTx(0, &nonce), 683 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 684 } 685 require.NoError(t, mp.Add(r5.FallbackTransaction, fs, r5)) 686 require.True(t, mp.ContainsKey(r5.FallbackTransaction.Hash())) 687 data, ok = mp.TryGetData(r5.FallbackTransaction.Hash()) 688 require.True(t, ok) 689 require.Equal(t, r5, data) 690 691 // and both r2's and r4's data should still be reachable 692 data, ok = mp.TryGetData(r2.FallbackTransaction.Hash()) 693 require.True(t, ok) 694 require.Equal(t, r2, data) 695 data, ok = mp.TryGetData(r4.FallbackTransaction.Hash()) 696 require.True(t, ok) 697 require.Equal(t, r4, data) 698 699 // should fail to get unexisting data 700 _, ok = mp.TryGetData(util.Uint256{0, 0, 0}) 701 require.False(t, ok) 702 703 // but getting nil data is OK. The resulting mp.verifiedTxes: [r3, r2, r4, r5, r6] 704 r6 := mkTwoSignersTx(smallNetFee, &nonce) 705 require.NoError(t, mp.Add(r6, fs, nil)) 706 require.True(t, mp.ContainsKey(r6.Hash())) 707 data, ok = mp.TryGetData(r6.Hash()) 708 require.True(t, ok) 709 require.Nil(t, data) 710 711 // getting data: item is in verifiedMap, but not in verifiedTxes 712 r7 := &payload.P2PNotaryRequest{ 713 MainTransaction: mkTwoSignersTx(0, &nonce), 714 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 715 } 716 require.NoError(t, mp.Add(r7.FallbackTransaction, fs, r4)) 717 require.True(t, mp.ContainsKey(r7.FallbackTransaction.Hash())) 718 r8 := &payload.P2PNotaryRequest{ 719 MainTransaction: mkTwoSignersTx(0, &nonce), 720 FallbackTransaction: mkTwoSignersTx(smallNetFee-1, &nonce), 721 } 722 require.NoError(t, mp.Add(r8.FallbackTransaction, fs, r4)) 723 require.True(t, mp.ContainsKey(r8.FallbackTransaction.Hash())) 724 mp.verifiedTxes = append(mp.verifiedTxes[:len(mp.verifiedTxes)-2], mp.verifiedTxes[len(mp.verifiedTxes)-1]) 725 _, ok = mp.TryGetData(r7.FallbackTransaction.Hash()) 726 require.False(t, ok) 727 } 728 729 func mkTwoSignersTx(netFee int64, nonce *uint32) *transaction.Transaction { 730 tx := transaction.New([]byte{byte(opcode.RET)}, 0) 731 tx.Signers = []transaction.Signer{{}, {}} 732 tx.NetworkFee = netFee 733 *nonce++ 734 tx.Nonce = *nonce 735 return tx 736 } 737 738 func TestMempoolIterateVerifiedTransactions(t *testing.T) { 739 var ( 740 smallNetFee int64 = 3 741 nonce uint32 742 r1, r2, r3, r4, r5 *payload.P2PNotaryRequest 743 ) 744 fs := &FeerStub{ 745 feePerByte: 0, 746 p2pSigExt: true, 747 blockHeight: 5, 748 balance: 100, 749 } 750 mp := New(10, 1, false, nil) 751 752 checkRequestsOrder := func(orderedRequests []*payload.P2PNotaryRequest) { 753 var pooledRequests []*payload.P2PNotaryRequest 754 mp.IterateVerifiedTransactions(func(tx *transaction.Transaction, data any) bool { 755 d := data.(*payload.P2PNotaryRequest) 756 pooledRequests = append(pooledRequests, d) 757 return true 758 }) 759 require.Equal(t, orderedRequests, pooledRequests) 760 } 761 762 r1 = &payload.P2PNotaryRequest{ 763 MainTransaction: mkTwoSignersTx(0, &nonce), 764 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 765 } 766 require.NoError(t, mp.Add(r1.FallbackTransaction, fs, r1)) 767 checkRequestsOrder([]*payload.P2PNotaryRequest{r1}) 768 769 // r2 has higher priority than r1. The resulting mp.verifiedTxes: [r2, r1] 770 r2 = &payload.P2PNotaryRequest{ 771 MainTransaction: mkTwoSignersTx(0, &nonce), 772 FallbackTransaction: mkTwoSignersTx(smallNetFee+1, &nonce), 773 } 774 require.NoError(t, mp.Add(r2.FallbackTransaction, fs, r2)) 775 checkRequestsOrder([]*payload.P2PNotaryRequest{r2, r1}) 776 777 // r3 has the same priority as r1. The resulting mp.verifiedTxes: [r2, r1, r3] 778 r3 = &payload.P2PNotaryRequest{ 779 MainTransaction: mkTwoSignersTx(0, &nonce), 780 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 781 } 782 require.NoError(t, mp.Add(r3.FallbackTransaction, fs, r3)) 783 checkRequestsOrder([]*payload.P2PNotaryRequest{r2, r1, r3}) 784 785 // r4 has the same priority as r1. The resulting mp.verifiedTxes: [r2, r1, r3, r4] 786 r4 = &payload.P2PNotaryRequest{ 787 MainTransaction: mkTwoSignersTx(0, &nonce), 788 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 789 } 790 require.NoError(t, mp.Add(r4.FallbackTransaction, fs, r4)) 791 checkRequestsOrder([]*payload.P2PNotaryRequest{r2, r1, r3, r4}) 792 793 checkPooledRequest := func(t *testing.T, r *payload.P2PNotaryRequest, isPooled bool) { 794 cont := true 795 notaryRequest := &payload.P2PNotaryRequest{} 796 mp.IterateVerifiedTransactions(func(tx *transaction.Transaction, data any) bool { 797 if data != nil { 798 notaryRequest = data.(*payload.P2PNotaryRequest) 799 if notaryRequest.MainTransaction.Hash() == r.MainTransaction.Hash() { 800 cont = false 801 } 802 } 803 return cont 804 }) 805 806 if isPooled { 807 require.Equal(t, false, cont) 808 require.Equal(t, r, notaryRequest) 809 } else { 810 require.Equal(t, true, cont) 811 } 812 } 813 checkPooledRequest(t, r1, true) 814 checkPooledRequest(t, r2, true) 815 checkPooledRequest(t, r3, true) 816 checkPooledRequest(t, r4, true) 817 818 r5 = &payload.P2PNotaryRequest{ 819 MainTransaction: mkTwoSignersTx(0, &nonce), 820 FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), 821 } 822 checkPooledRequest(t, r5, false) 823 }