github.com/aergoio/aergo@v1.3.1/mempool/mempool_test.go (about) 1 /** 2 * @file 3 * @copyright defined in aergo/LICENSE.txt 4 */ 5 package mempool 6 7 import ( 8 "encoding/binary" 9 "math/big" 10 "math/rand" 11 "os" 12 "sync/atomic" 13 "testing" 14 15 "github.com/aergoio/aergo/account/key" 16 "github.com/aergoio/aergo/config" 17 "github.com/aergoio/aergo/types" 18 "github.com/btcsuite/btcd/btcec" 19 "github.com/stretchr/testify/assert" 20 ) 21 22 const ( 23 maxAccount = 1000 24 maxRecipient = 1000 25 maxBlockBodySize = 10485760 26 ) 27 28 var ( 29 pool *MemPool 30 accs [maxAccount][]byte 31 sign [maxAccount]*btcec.PrivateKey 32 recipient [maxRecipient][]byte 33 ) 34 35 func _itobU32(argv uint32) []byte { 36 bs := make([]byte, 4) 37 binary.LittleEndian.PutUint32(bs, argv) 38 return bs 39 } 40 41 func getAccount(tx *types.Tx) string { 42 ab := tx.GetBody().GetAccount() 43 aid := types.ToAccountID(ab) 44 as := aid.String() 45 return as 46 } 47 48 func simulateBlockGen(txs ...types.Transaction) error { 49 lock.Lock() 50 inblock := make([]*types.Tx, 0) 51 for _, tx := range txs { 52 inblock = append(inblock, tx.GetTx()) 53 acc := getAccount(tx.GetTx()) 54 n := tx.GetBody().GetNonce() 55 nonce[acc] = n 56 _, ok := balance[acc] 57 if !ok { 58 balance[acc] = defaultBalance 59 } 60 balance[acc] -= tx.GetBody().GetAmountBigInt().Uint64() 61 } 62 lock.Unlock() 63 pool.removeOnBlockArrival( 64 &types.Block{ 65 Body: &types.BlockBody{ 66 Txs: inblock, 67 }}) 68 69 //bestBlockNo++ 70 return nil 71 } 72 func initTest(t *testing.T) { 73 serverCtx := config.NewServerContext("", "") 74 cfg := serverCtx.GetDefaultConfig().(*config.Config) 75 pool = NewMemPoolService(cfg, nil) 76 pool.testConfig = true 77 pool.BeforeStart() 78 79 for i := 0; i < maxAccount; i++ { 80 privkey, err := btcec.NewPrivateKey(btcec.S256()) 81 if err != nil { 82 t.Fatalf("failed to init test (%s)", err) 83 } 84 //gen new address 85 accs[i] = key.GenerateAddress(&privkey.PublicKey) 86 sign[i] = privkey 87 recipient[i] = _itobU32(uint32(i)) 88 } 89 } 90 func deinitTest() { 91 92 } 93 94 func sameTx(a *types.Tx, b *types.Tx) bool { 95 return types.ToTxID(a.Hash) == types.ToTxID(b.Hash) 96 } 97 func sameTxs(a []types.Transaction, b []types.Transaction) bool { 98 if len(a) != len(b) { 99 return false 100 } 101 check := false 102 for _, txa := range a { 103 check = false 104 for _, txb := range b { 105 if sameTx(txa.GetTx(), txb.GetTx()) { 106 check = true 107 break 108 } 109 } 110 if !check { 111 break 112 } 113 } 114 return check 115 } 116 func genTx(acc int, rec int, nonce uint64, amount uint64) types.Transaction { 117 tx := types.Tx{ 118 Body: &types.TxBody{ 119 Nonce: nonce, 120 Account: accs[acc], 121 Recipient: recipient[rec], 122 Amount: new(big.Int).SetUint64(amount).Bytes(), 123 }, 124 } 125 tx.Hash = tx.CalculateTxHash() 126 //key.SignTx(&tx, sign[acc]) 127 return types.NewTransaction(&tx) 128 } 129 130 /* 131 func TestTxSize(t *testing.T) { 132 initTest(t) 133 defer deinitTest() 134 135 var b []byte 136 b = make([]byte, txMaxSize) 137 tx := &types.Tx{ 138 Body: &types.TxBody{ 139 Nonce: 1, 140 Account: accs[0], 141 Recipient: recipient[0], 142 Amount: new(big.Int).SetUint64(1).Bytes(), 143 Payload: b, 144 }, 145 } 146 tx.Hash = tx.CalculateTxHash() 147 err := pool.put(tx) 148 assert.EqualError(t, err, types.ErrTxSizeExceedLimit.Error(), "wrong err") 149 } 150 */ 151 152 func TestInvalidTransaction(t *testing.T) { 153 154 initTest(t) 155 defer deinitTest() 156 err := pool.put(genTx(0, 1, 1, defaultBalance*2)) 157 assert.EqualError(t, err, types.ErrInsufficientBalance.Error(), "wrong err") 158 159 err = pool.put(genTx(0, 1, 1, 1)) 160 assert.NoError(t, err, "tx should be accepted") 161 162 err = pool.put(genTx(0, 1, 1, 1)) 163 assert.EqualError(t, err, types.ErrTxAlreadyInMempool.Error(), "tx should be denied") 164 165 txs := []types.Transaction{genTx(0, 1, 1, 1)} 166 simulateBlockGen(txs...) 167 168 err = pool.put(genTx(0, 1, 1, 1)) 169 assert.EqualError(t, err, types.ErrTxNonceTooLow.Error(), "tx should be denied") 170 } 171 172 /* 173 func TestInvalidTransactions(t *testing.T) { 174 initTest(t) 175 defer deinitTest() 176 tx := genTx(0, 1, 1, 1) 177 178 key.SignTx(tx, sign[1]) 179 err := pool.put(tx) 180 if err == nil { 181 t.Errorf("put invalid tx should be failed") 182 } 183 184 tx.Body.Sign = nil 185 tx.Hash = tx.CalculateTxHash() 186 187 err = pool.put(tx) 188 if err == nil { 189 t.Errorf("put invalid tx should be failed") 190 } 191 } 192 */ 193 194 func TestOrphanTransaction(t *testing.T) { 195 196 initTest(t) 197 defer deinitTest() 198 199 err := pool.put(genTx(0, 1, 1, 2)) 200 assert.NoError(t, err, "tx should be accepted") 201 202 // tx inject order : 1 3 5 2 4 10 9 8 7 6 203 // non-sequential nonce should be accepted (orphan) but not counted 204 err = pool.put(genTx(0, 1, 3, 2)) 205 assert.NoError(t, err, "tx should be accepted") 206 207 err = pool.put(genTx(0, 1, 5, 2)) 208 assert.NoError(t, err, "tx should be accepted") 209 210 total, orphan := pool.Size() 211 assert.EqualValuesf(t, []int{total, orphan}, []int{3, 2}, "wrong mempool stat") 212 213 err = pool.put(genTx(0, 1, 2, 2)) 214 assert.NoError(t, err, "tx should be accepted") 215 216 total, orphan = pool.Size() 217 assert.EqualValuesf(t, []int{total, orphan}, []int{4, 1}, "wrong mempool stat") 218 219 err = pool.put(genTx(0, 1, 4, 2)) 220 assert.NoError(t, err, "tx should be accepted") 221 222 total, orphan = pool.Size() 223 assert.EqualValuesf(t, []int{total, orphan}, []int{5, 0}, "wrong mempool stat") 224 225 err = pool.put(genTx(0, 1, 10, 2)) 226 assert.NoError(t, err, "tx should be accepted") 227 228 err = pool.put(genTx(0, 1, 9, 2)) 229 assert.NoError(t, err, "tx should be accepted") 230 231 err = pool.put(genTx(0, 1, 8, 2)) 232 assert.NoError(t, err, "tx should be accepted") 233 234 err = pool.put(genTx(0, 1, 7, 2)) 235 assert.NoError(t, err, "tx should be accepted") 236 237 total, orphan = pool.Size() 238 assert.EqualValuesf(t, []int{total, orphan}, []int{9, 4}, "wrong mempool stat") 239 240 err = pool.put(genTx(0, 1, 6, 2)) 241 assert.NoError(t, err, "tx should be accepted") 242 243 total, orphan = pool.Size() 244 assert.EqualValuesf(t, []int{total, orphan}, []int{10, 0}, "wrong mempool stat") 245 } 246 247 func TestBasics2(t *testing.T) { 248 initTest(t) 249 defer deinitTest() 250 txs := make([]*types.Tx, 0) 251 252 accCount := 1000 253 txCount := 1000 254 nonce := make([]uint64, txCount) 255 for i := 0; i < txCount; i++ { 256 nonce[i] = uint64(i + 1) 257 } 258 259 for i := 0; i < accCount; i++ { 260 rand.Shuffle(txCount, func(i, j int) { 261 nonce[i], nonce[j] = nonce[j], nonce[i] 262 }) 263 for j := 0; j < txCount; j++ { 264 tmp := genTx(i, 0, nonce[j], uint64(i+1)) 265 txs = append(txs, tmp.GetTx()) 266 } 267 } 268 269 for _, tx := range txs { 270 err := pool.put(types.NewTransaction(tx)) 271 assert.NoError(t, err, "tx should be accepted") 272 } 273 274 txsMempool, err := pool.get(maxBlockBodySize * 10) 275 assert.NoError(t, err, "get failed") 276 assert.Equal(t, len(txsMempool), len(txs)) 277 } 278 279 // gen sequential transactions 280 // check mempool internal states 281 func TestBasics(t *testing.T) { 282 initTest(t) 283 defer deinitTest() 284 txs := make([]types.Transaction, 0) 285 286 accCount := 10 287 txCount := 10 288 nonce := make([]uint64, txCount) 289 for i := 0; i < txCount; i++ { 290 nonce[i] = uint64(i + 1) 291 } 292 for i := 0; i < accCount; i++ { 293 rand.Shuffle(txCount, func(i, j int) { 294 nonce[i], nonce[j] = nonce[j], nonce[i] 295 }) 296 for j := 0; j < txCount; j++ { 297 tmp := genTx(i, 0, nonce[j], uint64(i+1)) 298 txs = append(txs, tmp) 299 } 300 } 301 302 errs := pool.puts(txs...) 303 assert.Equal(t, len(errs), accCount*txCount, "error length is different") 304 305 for i := 0; i < len(errs); i++ { 306 assert.NoError(t, errs[i], "%dth tx failed", i) 307 } 308 309 txsMempool, err := pool.get(maxBlockBodySize) 310 assert.NoError(t, err, "get failed") 311 assert.Equal(t, len(txsMempool), len(txs)) 312 } 313 314 func TestDeleteOTxs(t *testing.T) { 315 initTest(t) 316 defer deinitTest() 317 txs := make([]types.Transaction, 0) 318 for i := 0; i < 5; i++ { 319 tmp := genTx(0, 0, uint64(i+1), uint64(i+1)) 320 txs = append(txs, tmp) 321 } 322 pool.puts(txs...) 323 324 total, orphan := pool.Size() 325 assert.EqualValuesf(t, []int{total, orphan}, []int{5, 0}, "wrong mempool stat") 326 327 txs[4] = genTx(0, 1, 5, 150) 328 simulateBlockGen(txs...) 329 330 total, orphan = pool.Size() 331 assert.EqualValuesf(t, []int{total, orphan}, []int{0, 0}, "wrong mempool stat") 332 } 333 334 // add 100 sequential txs and simulate to generate block 10time. 335 // each block contains 10 txs 336 func TestBasicDeleteOnBlockConnect(t *testing.T) { 337 initTest(t) 338 defer deinitTest() 339 txs := make([]types.Transaction, 0) 340 341 for i := 0; i < 100; i++ { 342 tmp := genTx(0, 0, uint64(i+1), uint64(i+1)) 343 txs = append(txs, tmp) 344 } 345 pool.puts(txs...) 346 347 total, orphan := pool.Size() 348 assert.EqualValuesf(t, []int{total, orphan}, []int{100, 0}, "wrong mempool stat") 349 350 //suppose 10 txs are select into new block 351 for j := 0; j < 10; j++ { 352 simulateBlockGen(txs[:10]...) 353 354 total, orphan := pool.Size() 355 assert.EqualValuesf(t, []int{total, orphan}, []int{10 * (9 - j), 0}, "wrong mempool stat") 356 357 removed := txs[:10] 358 359 for _, tx := range removed { 360 found := pool.exist(tx.GetHash()) 361 assert.Nil(t, found, "wrong transaction removed") 362 } 363 364 leftover := txs[10:] 365 for _, tx := range leftover { 366 found := pool.exist(tx.GetHash()) 367 assert.NotNil(t, found, "wrong transaction removed") 368 } 369 txs = txs[10:] 370 } 371 372 l, e := pool.get(maxBlockBodySize) 373 assert.NoError(t, e, "get should succeed") 374 assert.Equalf(t, len(l), 0, "leftover found") 375 } 376 377 func TestDeleteInvokeRearrange(t *testing.T) { 378 379 initTest(t) 380 defer deinitTest() 381 txs := make([]types.Transaction, 0) 382 383 missing := map[int]bool{ 384 7: true, 8: true, 9: true, 385 17: true, 18: true, 19: true, 386 27: true, 28: true, 29: true, 387 33: true, 34: true, 35: true, 388 50: true} 389 390 for i := 1; i < 51; i++ { 391 tmp := genTx(0, 0, uint64(i), uint64(i)) 392 txs = append(txs, tmp) 393 if _, v := missing[i]; v { 394 continue 395 } 396 assert.NoError(t, pool.put(tmp), "tx should be accepted") 397 } 398 399 total, orphan := pool.Size() 400 assert.EqualValuesf(t, []int{total, orphan}, []int{37, 31}, "wrong mempool stat") 401 402 // txs currently 403 // ready: 1~6 orphan: 10~16, 20~26, 30~32, 36~49 404 // test senario : check boundary, middle, end of each tx chunk 405 // 1. gen block including 1~4 406 // 2. gen block including 5~8 407 // 3. gen block including 9~13 408 // 4. gen block including 14~28 409 // 5. gen block including 29~30 410 // 6. gen block including 31~32 411 // 7. gen block including 33~35 412 // 8. gen blocin including ~50 413 start := []int{1, 5, 9, 14, 29, 31, 33, 36} 414 end := []int{4, 8, 13, 28, 30, 32, 35, 50} 415 for i := 0; i < len(start); i++ { 416 s, e := start[i]-1, end[i] 417 simulateBlockGen(txs[s:e]...) 418 419 //p1, p2 := pool.Size() 420 //t.Errorf("%d, %d, %d", i, p1, p2) 421 removed := txs[s:e] 422 for _, tx := range removed { 423 found := pool.exist(tx.GetHash()) 424 assert.Nil(t, found, "wrong transaction removed") 425 } 426 427 leftover := txs[e:] 428 for _, tx := range leftover { 429 n := tx.GetBody().GetNonce() 430 if _, v := missing[int(n)]; v { 431 continue 432 } 433 if pool.exist(tx.GetHash()) == nil { 434 t.Errorf("wrong tx removed [%s]", tx.GetBody().String()) 435 } 436 } 437 } 438 } 439 440 func TestSwitchingBestBlock(t *testing.T) { 441 initTest(t) 442 defer deinitTest() 443 444 txs := make([]types.Transaction, 0) 445 tx0 := genTx(0, 1, 1, 1) 446 tx1 := genTx(0, 1, 2, 1) 447 txs = append(txs, tx0, tx1) 448 449 err := pool.puts(txs...) 450 if len(err) != 2 || err[0] != nil || err[1] != nil { 451 t.Errorf("put should succeed, %s", err) 452 } 453 simulateBlockGen(txs...) 454 455 tx2 := genTx(0, 1, 3, 1) 456 if err := pool.put(tx2); err != nil { 457 t.Errorf("put should succeed, %s", err) 458 } 459 ready, orphan := pool.Size() 460 if ready != 1 || orphan != 0 { 461 t.Errorf("size wrong:%d, %d", ready, orphan) 462 } 463 464 simulateBlockGen(txs[:1]...) 465 466 ready, orphan = pool.Size() 467 if ready != 1 || orphan != 1 { 468 t.Errorf("size wrong:%d, %d", ready, orphan) 469 } 470 471 tx4 := genTx(0, 1, 5, 1) 472 if err := pool.put(tx4); err != nil { 473 t.Errorf("put should succeed, %s", err.Error()) 474 } 475 476 ready, orphan = pool.Size() 477 if ready != 2 || orphan != 2 { 478 t.Errorf("size wrong:%d, %d", ready, orphan) 479 } 480 481 if err := pool.put(tx1); err != nil { 482 t.Errorf("put should succeed, %s", err.Error()) 483 } 484 ready, orphan = pool.Size() 485 if ready != 3 || orphan != 1 { 486 t.Errorf("size wrong:%d, %d", ready, orphan) 487 } 488 } 489 490 func TestDumpAndLoad(t *testing.T) { 491 initTest(t) 492 //set temporary path for test 493 pool.dumpPath = "./mempool_dump_test" 494 txs := make([]*types.Tx, 0) 495 496 if _, err := os.Stat(pool.dumpPath); os.IsExist(err) { 497 if os.Remove(pool.dumpPath) != nil { 498 t.Errorf("init test failed (rm %s failed)", pool.dumpPath) 499 } 500 } 501 502 pool.dumpTxsToFile() 503 if _, err := os.Stat(pool.dumpPath); err != nil && !os.IsNotExist(err) { 504 t.Errorf("err should be NotExist ,but %s", err.Error()) 505 } 506 507 if !atomic.CompareAndSwapInt32(&pool.status, initial, running) { 508 t.Errorf("pool status should be initial, but %d", pool.status) 509 } 510 pool.dumpTxsToFile() 511 if _, err := os.Stat(pool.dumpPath); err != nil && !os.IsNotExist(err) { 512 t.Errorf("err should be NotExist ,but %s", err.Error()) 513 } 514 515 for i := 0; i < 100; i++ { 516 tmp := genTx(0, 0, uint64(i+1), uint64(i+1)) 517 txs = append(txs, tmp.GetTx()) 518 if err := pool.put(tmp); err != nil { 519 t.Errorf("put should succeed, %s", err.Error()) 520 } 521 } 522 523 pool.dumpTxsToFile() 524 if _, err := os.Stat(pool.dumpPath); err != nil { 525 t.Errorf("dump file should be created but, %s", err.Error()) 526 } 527 deinitTest() 528 529 initTest(t) 530 pool.dumpPath = "./mempool_dump_test" 531 ready, orphan := pool.Size() 532 if ready != 0 || orphan != 0 { 533 t.Errorf("size wrong:%d, %d", ready, orphan) 534 } 535 if !atomic.CompareAndSwapInt32(&pool.status, initial, running) { 536 t.Errorf("pool status should be initial, but %d", pool.status) 537 } 538 pool.loadTxs() 539 ready, orphan = pool.Size() 540 if ready != 0 || orphan != 0 { 541 t.Errorf("size wrong:%d, %d", ready, orphan) 542 } 543 544 if !atomic.CompareAndSwapInt32(&pool.status, running, initial) { 545 t.Errorf("pool status should be initial, but %d", pool.status) 546 } 547 548 pool.loadTxs() 549 ready, orphan = pool.Size() 550 if ready != 100 || orphan != 0 { 551 t.Errorf("size wrong:%d, %d", ready, orphan) 552 } 553 deinitTest() 554 os.Remove(pool.dumpPath) // nolint: errcheck 555 } 556 557 func TestEvictOnProfit(t *testing.T) { 558 initTest(t) 559 defer deinitTest() 560 561 if err := pool.put(genTx(0, 0, 1, 3)); err != nil { 562 t.Errorf("put should succeed, %s", err.Error()) 563 } 564 if err := pool.put(genTx(0, 0, 1, 10)); err == nil { 565 t.Errorf("put should failed") //FIXME 566 } 567 568 if err := pool.put(genTx(0, 0, 5, 3)); err != nil { 569 t.Errorf("put should succeed, %s", err.Error()) 570 } 571 pool.put(genTx(0, 0, 6, 3)) 572 pool.put(genTx(0, 0, 7, 3)) 573 574 if err := pool.put(genTx(0, 0, 6, 10)); err == nil { 575 t.Errorf("put should failed") // FIXME 576 } 577 } 578 579 func TestDeleteInvokePriceFilterOut(t *testing.T) { 580 initTest(t) 581 defer deinitTest() 582 583 checkRemainder := func(total int, orphan int) { 584 w, o := pool.Size() 585 if w != total || o != orphan { 586 t.Fatalf("pool should have %d tx(%d orphans) but(%d/%d)\n", total, orphan, w, o) 587 } 588 } 589 txs := make([]types.Transaction, 0) 590 txs = append(txs, genTx(0, 1, 1, defaultBalance-6)) 591 txs = append(txs, genTx(0, 1, 2, 2)) 592 txs = append(txs, genTx(0, 1, 3, 10)) 593 txs = append(txs, genTx(0, 1, 4, 5)) 594 595 for _, tx := range txs { 596 pool.put(tx) 597 } 598 checkRemainder(len(txs), 0) 599 simulateBlockGen(txs[:1]...) 600 601 checkRemainder(2, 1) 602 simulateBlockGen(txs[1:2]...) 603 checkRemainder(0, 0) 604 }