gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/transactionpool/transactionpool_test.go (about) 1 package transactionpool 2 3 import ( 4 "path/filepath" 5 "testing" 6 7 "gitlab.com/NebulousLabs/fastrand" 8 "gitlab.com/SiaPrime/SiaPrime/build" 9 "gitlab.com/SiaPrime/SiaPrime/crypto" 10 "gitlab.com/SiaPrime/SiaPrime/modules" 11 "gitlab.com/SiaPrime/SiaPrime/modules/consensus" 12 "gitlab.com/SiaPrime/SiaPrime/modules/gateway" 13 "gitlab.com/SiaPrime/SiaPrime/modules/miner" 14 "gitlab.com/SiaPrime/SiaPrime/modules/wallet" 15 "gitlab.com/SiaPrime/SiaPrime/types" 16 ) 17 18 // A tpoolTester is used during testing to initialize a transaction pool and 19 // useful helper modules. 20 type tpoolTester struct { 21 cs modules.ConsensusSet 22 gateway modules.Gateway 23 tpool *TransactionPool 24 miner modules.TestMiner 25 wallet modules.Wallet 26 walletKey crypto.CipherKey 27 28 persistDir string 29 } 30 31 // blankTpoolTester returns a ready-to-use tpool tester, with all modules 32 // initialized, without mining a block. 33 func blankTpoolTester(name string) (*tpoolTester, error) { 34 // Initialize the modules. 35 testdir := build.TempDir(modules.TransactionPoolDir, name) 36 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 37 if err != nil { 38 return nil, err 39 } 40 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 41 if err != nil { 42 return nil, err 43 } 44 tp, err := New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 45 if err != nil { 46 return nil, err 47 } 48 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 49 if err != nil { 50 return nil, err 51 } 52 key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 53 _, err = w.Encrypt(key) 54 if err != nil { 55 return nil, err 56 } 57 err = w.Unlock(key) 58 if err != nil { 59 return nil, err 60 } 61 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 62 if err != nil { 63 return nil, err 64 } 65 66 // Assemble all of the objects into a tpoolTester 67 return &tpoolTester{ 68 cs: cs, 69 gateway: g, 70 tpool: tp, 71 miner: m, 72 wallet: w, 73 walletKey: key, 74 75 persistDir: testdir, 76 }, nil 77 } 78 79 // createTpoolTester returns a ready-to-use tpool tester, with all modules 80 // initialized. 81 func createTpoolTester(name string) (*tpoolTester, error) { 82 tpt, err := blankTpoolTester(name) 83 if err != nil { 84 return nil, err 85 } 86 87 // Mine blocks until there is money in the wallet. 88 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 89 b, _ := tpt.miner.FindBlock() 90 err = tpt.cs.AcceptBlock(b) 91 if err != nil { 92 return nil, err 93 } 94 } 95 96 return tpt, nil 97 } 98 99 // Close safely closes the tpoolTester, calling a panic in the event of an 100 // error since there isn't a good way to errcheck when deferring a Close. 101 func (tpt *tpoolTester) Close() error { 102 errs := []error{ 103 tpt.cs.Close(), 104 tpt.gateway.Close(), 105 tpt.tpool.Close(), 106 tpt.miner.Close(), 107 tpt.wallet.Close(), 108 } 109 if err := build.JoinErrors(errs, "; "); err != nil { 110 panic(err) 111 } 112 return nil 113 } 114 115 // TestIntegrationNewNilInputs tries to trigger a panic with nil inputs. 116 func TestIntegrationNewNilInputs(t *testing.T) { 117 if testing.Short() { 118 t.SkipNow() 119 } 120 // Create a gateway and consensus set. 121 testdir := build.TempDir(modules.TransactionPoolDir, t.Name()) 122 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 123 if err != nil { 124 t.Fatal(err) 125 } 126 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 127 if err != nil { 128 t.Fatal(err) 129 } 130 tpDir := filepath.Join(testdir, modules.TransactionPoolDir) 131 132 // Try all combinations of nil inputs. 133 _, err = New(nil, nil, tpDir) 134 if err == nil { 135 t.Error(err) 136 } 137 _, err = New(nil, g, tpDir) 138 if err != errNilCS { 139 t.Error(err) 140 } 141 _, err = New(cs, nil, tpDir) 142 if err != errNilGateway { 143 t.Error(err) 144 } 145 _, err = New(cs, g, tpDir) 146 if err != nil { 147 t.Error(err) 148 } 149 } 150 151 // TestGetTransaction verifies that the transaction pool's Transaction() method 152 // works correctly. 153 func TestGetTransaction(t *testing.T) { 154 if testing.Short() { 155 t.SkipNow() 156 } 157 tpt, err := createTpoolTester(t.Name()) 158 if err != nil { 159 t.Fatal(err) 160 } 161 defer tpt.Close() 162 163 value := types.NewCurrency64(35e6) 164 fee := types.NewCurrency64(3e2) 165 emptyUH := types.UnlockConditions{}.UnlockHash() 166 txnBuilder, err := tpt.wallet.StartTransaction() 167 if err != nil { 168 t.Fatal(err) 169 } 170 err = txnBuilder.FundSiacoins(value) 171 if err != nil { 172 t.Fatal(err) 173 } 174 txnBuilder.AddMinerFee(fee) 175 output := types.SiacoinOutput{ 176 Value: value.Sub(fee), 177 UnlockHash: emptyUH, 178 } 179 txnBuilder.AddSiacoinOutput(output) 180 txnSet, err := txnBuilder.Sign(true) 181 if err != nil { 182 t.Fatal(err) 183 } 184 childrenSet := []types.Transaction{{ 185 SiacoinInputs: []types.SiacoinInput{{ 186 ParentID: txnSet[len(txnSet)-1].SiacoinOutputID(0), 187 }}, 188 SiacoinOutputs: []types.SiacoinOutput{{ 189 Value: value.Sub(fee), 190 UnlockHash: emptyUH, 191 }}, 192 }} 193 194 superSet := append(txnSet, childrenSet...) 195 err = tpt.tpool.AcceptTransactionSet(superSet) 196 if err != nil { 197 t.Fatal(err) 198 } 199 200 targetTxn := childrenSet[0] 201 txn, parents, exists := tpt.tpool.Transaction(targetTxn.ID()) 202 if !exists { 203 t.Fatal("transaction set did not exist") 204 } 205 if txn.ID() != targetTxn.ID() { 206 t.Fatal("returned the wrong transaction") 207 } 208 if len(parents) != len(txnSet) { 209 t.Fatal("transaction had wrong number of parents") 210 } 211 for i, txn := range txnSet { 212 if parents[i].ID() != txn.ID() { 213 t.Fatal("returned the wrong parent") 214 } 215 } 216 } 217 218 // TestBlockFeeEstimation checks that the fee estimation algorithm is reasonably 219 // on target when the tpool is relying on blockchain based fee estimation. 220 func TestFeeEstimation(t *testing.T) { 221 if testing.Short() || !build.VLONG { 222 t.Skip("Tpool is too slow to run this test regularly") 223 } 224 tpt, err := createTpoolTester(t.Name()) 225 if err != nil { 226 t.Fatal(err) 227 } 228 defer tpt.Close() 229 230 // Prepare a bunch of outputs for a series of graphs to fill up the 231 // transaction pool. 232 graphLens := 400 // 80 kb per graph 233 numGraphs := int(types.BlockSizeLimit) * blockFeeEstimationDepth / (graphLens * 206) // Enough to fill 'estimation depth' blocks. 234 graphFund := types.SiacoinPrecision.Mul64(1000) 235 var outputs []types.SiacoinOutput 236 for i := 0; i < numGraphs+1; i++ { 237 outputs = append(outputs, types.SiacoinOutput{ 238 UnlockHash: types.UnlockConditions{}.UnlockHash(), 239 Value: graphFund, 240 }) 241 } 242 txns, err := tpt.wallet.SendSiacoinsMulti(outputs) 243 if err != nil { 244 t.Error(err) 245 } 246 247 // Mine the graph setup in the consensus set so that the graph outputs are 248 // distinct transaction sets. 249 _, err = tpt.miner.AddBlock() 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 // Create all of the graphs. 255 finalTxn := txns[len(txns)-1] 256 var graphs [][]types.Transaction 257 for i := 0; i < numGraphs; i++ { 258 var edges []types.TransactionGraphEdge 259 var cumFee types.Currency 260 for j := 0; j < graphLens; j++ { 261 fee := types.SiacoinPrecision.Mul64(uint64(j + i + 1)).Div64(200) 262 cumFee = cumFee.Add(fee) 263 edges = append(edges, types.TransactionGraphEdge{ 264 Dest: j + 1, 265 Fee: fee, 266 Source: j, 267 Value: graphFund.Sub(cumFee), 268 }) 269 } 270 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 271 if err != nil { 272 t.Fatal(err) 273 } 274 graphs = append(graphs, graph) 275 } 276 277 // One block at a time, add graphs to the tpool and blockchain. Then check 278 // the median fee estimation and see that it's the right value. 279 var prevMin types.Currency 280 for i := 0; i < blockFeeEstimationDepth; i++ { 281 // Insert enough graphs to fill a block. 282 for j := 0; j < numGraphs/blockFeeEstimationDepth; j++ { 283 err = tpt.tpool.AcceptTransactionSet(graphs[0]) 284 if err != nil { 285 t.Fatal(err) 286 } 287 graphs = graphs[1:] 288 } 289 290 // Add a block to the transaction pool. 291 _, err = tpt.miner.AddBlock() 292 if err != nil { 293 t.Fatal(err) 294 } 295 296 // Check that max is always greater than min. 297 min, max := tpt.tpool.FeeEstimation() 298 if min.Cmp(max) > 0 { 299 t.Error("max fee is less than min fee estimation") 300 } 301 302 // If we're over halfway through the depth, the suggested fee should 303 // start to exceed the default. 304 if i > blockFeeEstimationDepth/2 { 305 if min.Cmp(minEstimation) <= 0 { 306 t.Error("fee estimation does not seem to be increasing") 307 } 308 if min.Cmp(prevMin) <= 0 { 309 t.Error("fee estimation does not seem to be increasing") 310 } 311 } 312 prevMin = min 313 314 // Reset the tpool to verify that the persist structures are 315 // functioning. 316 // 317 // TODO: For some reason, closing and re-opeining the tpool results in 318 // incredible performance penalties. 319 /* 320 err = tpt.tpool.Close() 321 if err != nil { 322 t.Fatal(err) 323 } 324 tpt.tpool, err = New(tpt.cs, tpt.gateway, tpt.persistDir) 325 if err != nil { 326 t.Fatal(err) 327 } 328 */ 329 } 330 331 // Mine a few blocks and then check that the fee estimation has returned to 332 // minimum as congestion clears up. 333 for i := 0; i < (blockFeeEstimationDepth/2)+1; i++ { 334 _, err = tpt.miner.AddBlock() 335 if err != nil { 336 t.Fatal(err) 337 } 338 } 339 min, _ := tpt.tpool.FeeEstimation() 340 if !(min.Cmp(minEstimation) == 0) { 341 t.Error("fee estimator does not seem to be reducing with empty blocks.") 342 } 343 } 344 345 // TestTpoolScalability fills the whole transaction pool with complex 346 // transactions, then mines enough blocks to empty it out. Running sequentially, 347 // the test should take less than 250ms per mb that the transaction pool fills 348 // up, and less than 250ms per mb to empty out - indicating linear scalability 349 // and tolerance for a larger pool size. 350 func TestTpoolScalability(t *testing.T) { 351 if testing.Short() || !build.VLONG { 352 t.Skip("Tpool is too slow to run this test regularly") 353 } 354 tpt, err := createTpoolTester(t.Name()) 355 if err != nil { 356 t.Fatal(err) 357 } 358 defer tpt.Close() 359 360 // Mine a few more blocks to get some extra funding. 361 for i := 0; i < 3; i++ { 362 _, err := tpt.miner.AddBlock() 363 if err != nil { 364 t.Fatal(err) 365 } 366 } 367 368 // Prepare a bunch of outputs for a series of graphs to fill up the 369 // transaction pool. 370 rows := 10 // needs to factor into exclusively '2's and '5's. 371 graphSize := 11796 // Measured with logging. Change if 'rows' changes. 372 numGraphs := TransactionPoolSizeTarget / graphSize // Enough to fill the transaction pool. 373 graphFund := types.SiacoinPrecision.Mul64(2000) 374 var outputs []types.SiacoinOutput 375 for i := 0; i < numGraphs+1; i++ { 376 outputs = append(outputs, types.SiacoinOutput{ 377 UnlockHash: types.UnlockConditions{}.UnlockHash(), 378 Value: graphFund, 379 }) 380 } 381 txns, err := tpt.wallet.SendSiacoinsMulti(outputs) 382 if err != nil { 383 t.Error(err) 384 } 385 386 // Mine the graph setup in the consensus set so that the graph outputs are 387 // distinct transaction sets. 388 _, err = tpt.miner.AddBlock() 389 if err != nil { 390 t.Fatal(err) 391 } 392 393 // Create all of the graphs. 394 finalTxn := txns[len(txns)-1] 395 var graphs [][]types.Transaction 396 for i := 0; i < numGraphs; i++ { 397 var edges []types.TransactionGraphEdge 398 399 // Create the root of the graph. 400 feeValues := types.SiacoinPrecision 401 firstRowValues := graphFund.Sub(feeValues.Mul64(uint64(rows))).Div64(uint64(rows)) 402 for j := 0; j < rows; j++ { 403 edges = append(edges, types.TransactionGraphEdge{ 404 Dest: j + 1, 405 Fee: types.SiacoinPrecision, 406 Source: 0, 407 Value: firstRowValues, 408 }) 409 } 410 411 // Create each row of the graph. 412 var firstNodeValue types.Currency 413 nodeIndex := 1 414 for j := 0; j < rows; j++ { 415 // Create the first node in the row, which has an increasing 416 // balance. 417 rowValue := firstRowValues.Sub(types.SiacoinPrecision.Mul64(uint64(j + 1))) 418 firstNodeValue = firstNodeValue.Add(rowValue) 419 edges = append(edges, types.TransactionGraphEdge{ 420 Dest: nodeIndex + (rows - j), 421 Fee: types.SiacoinPrecision, 422 Source: nodeIndex, 423 Value: firstNodeValue, 424 }) 425 nodeIndex++ 426 427 // Create the remaining nodes in this row. 428 for k := j + 1; k < rows; k++ { 429 edges = append(edges, types.TransactionGraphEdge{ 430 Dest: nodeIndex + (rows - (j + 1)), 431 Fee: types.SiacoinPrecision, 432 Source: nodeIndex, 433 Value: rowValue, 434 }) 435 nodeIndex++ 436 } 437 } 438 439 // Build the graph and add it to the stack of graphs. 440 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 441 if err != nil { 442 t.Fatal(err) 443 } 444 graphs = append(graphs, graph) 445 } 446 447 // Add all of the root transactions to the blockchain to throw off the 448 // parent math off for the transaction pool. 449 for _, graph := range graphs { 450 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{graph[0]}) 451 if err != nil { 452 t.Fatal(err) 453 } 454 } 455 _, err = tpt.miner.AddBlock() 456 if err != nil { 457 t.Fatal(err) 458 } 459 460 // Add all of the transactions in each graph into the tpool, one transaction 461 // at a time, interweaved, chaotically. 462 for i := 1; i < len(graphs[0]); i++ { 463 for j := 0; j < len(graphs); j++ { 464 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{graphs[j][i]}) 465 if err != nil { 466 t.Fatal(err, i, j) 467 } 468 } 469 } 470 471 // Mine blocks until the tpool is gone. 472 for tpt.tpool.transactionListSize > 0 { 473 _, err := tpt.miner.AddBlock() 474 if err != nil { 475 t.Fatal(err) 476 } 477 } 478 } 479 480 // TestHeapFees creates a large number of transaction graphs with increasing fee 481 // value. Then it checks that those sets with higher value transaction fees are 482 // prioritized for placement in blocks. 483 func TestHeapFees(t *testing.T) { 484 if testing.Short() || !build.VLONG { 485 t.SkipNow() 486 } 487 tpt, err := createTpoolTester(t.Name()) 488 if err != nil { 489 t.Fatal(err) 490 } 491 defer tpt.Close() 492 493 // Mine a few more blocks to get some extra funding. 494 for i := 0; i < 4; i++ { 495 _, err := tpt.miner.AddBlock() 496 if err != nil { 497 t.Fatal(err) 498 } 499 } 500 501 // Create transaction graph setup. 502 coinFrac := types.SiacoinPrecision 503 numGraphs := 110 504 graphFund := coinFrac.Mul64(12210) 505 var outputs []types.SiacoinOutput 506 for i := 0; i < numGraphs; i++ { 507 outputs = append(outputs, types.SiacoinOutput{ 508 UnlockHash: types.UnlockConditions{}.UnlockHash(), 509 Value: graphFund, 510 }) 511 } 512 txns, err := tpt.wallet.SendSiacoinsMulti(outputs) 513 if err != nil { 514 t.Error(err) 515 } 516 // Mine the graph setup in the consensus set so that the graph outputs are 517 // transaction sets. This guarantees that the parent of every graph will be 518 // its own output. 519 _, err = tpt.miner.AddBlock() 520 if err != nil { 521 t.Fatal(err) 522 } 523 finalTxn := txns[len(txns)-1] 524 // For each output, create 250 transactions 525 var graphs [][]types.Transaction 526 for i := 0; i < numGraphs; i++ { 527 var edges []types.TransactionGraphEdge 528 var cumFee types.Currency 529 for j := 0; j < numGraphs; j++ { 530 fee := coinFrac.Mul64(uint64((j + 1))) 531 cumFee = cumFee.Add(fee) 532 edges = append(edges, types.TransactionGraphEdge{ 533 Dest: j + 1, 534 Fee: fee, 535 Source: 0, 536 Value: fee, 537 }) 538 } 539 for k := 0; k < numGraphs; k++ { 540 fee := coinFrac.Mul64(uint64(k + 1)).Div64(2) 541 cumFee = cumFee.Add(fee) 542 edges = append(edges, types.TransactionGraphEdge{ 543 Dest: k + 251, 544 Fee: fee, 545 Source: k + 1, 546 Value: fee, 547 }) 548 } 549 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 550 if err != nil { 551 t.Fatal(err) 552 } 553 graphs = append(graphs, graph) 554 555 } 556 // Accept the parent node of each graph so that its outputs we can test 557 // spending its outputs after mining the next block. 558 for _, graph := range graphs { 559 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{graph[0]}) 560 if err != nil { 561 t.Fatal(err) 562 } 563 } 564 block, err := tpt.miner.AddBlock() 565 if err != nil { 566 t.Fatal(err) 567 } 568 569 // Now accept all the other nodes of each graph. 570 for _, graph := range graphs { 571 for _, txn := range graph[1:] { 572 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 573 if err != nil { 574 t.Fatal(err) 575 } 576 } 577 } 578 // Now we mine 2 blocks in sequence and check that higher fee transactions 579 // show up to the first block. 580 block, err = tpt.miner.AddBlock() 581 if err != nil { 582 t.Fatal(err) 583 } 584 585 var totalFee1 types.Currency 586 expectedFee1 := coinFrac.Mul64(321915) 587 588 // Add up total fees 589 numTxns1 := 0 590 maxFee1 := types.SiacoinPrecision.Div64(1000000) 591 minFee1 := types.SiacoinPrecision.Mul64(1000000) 592 for _, txn := range block.Transactions { 593 for _, fee := range txn.MinerFees { 594 if fee.Cmp(maxFee1) >= 0 { 595 maxFee1 = fee 596 } 597 if fee.Cmp(minFee1) <= 0 { 598 minFee1 = fee 599 } 600 totalFee1 = totalFee1.Add(fee) 601 numTxns1++ 602 } 603 } 604 avgFee1 := totalFee1.Div64(uint64(numTxns1)) 605 if totalFee1.Cmp(expectedFee1) != 0 { 606 t.Error("totalFee1 different than expected fee.", totalFee1.String(), expectedFee1.String()) 607 //t.Log(totalFee1.Sub(expectedFee1).HumanString()) 608 609 } 610 611 // Mine the next block so we can check the transactions inside 612 block, err = tpt.miner.AddBlock() 613 if err != nil { 614 t.Fatal(err) 615 } 616 617 var totalFee2 types.Currency 618 expectedFee2 := coinFrac.Mul64(13860) 619 620 // Add up total fees 621 numTxns2 := 0 622 maxFee2 := types.SiacoinPrecision.Div64(1000000) 623 minFee2 := types.SiacoinPrecision.Mul64(1000000) 624 for _, txn := range block.Transactions { 625 for _, fee := range txn.MinerFees { 626 if fee.Cmp(maxFee2) >= 0 { 627 maxFee2 = fee 628 } 629 if fee.Cmp(minFee2) <= 0 { 630 minFee2 = fee 631 } 632 totalFee2 = totalFee2.Add(fee) 633 numTxns2++ 634 } 635 } 636 avgFee2 := totalFee2.Div64(uint64(numTxns2)) 637 if totalFee2.Cmp(expectedFee2) != 0 { 638 t.Error("totalFee2 different than expected fee.", totalFee2.String(), expectedFee2.String()) 639 //t.Log(totalFee2.Sub(expectedFee2).HumanString()) 640 } 641 if avgFee1.Cmp(avgFee2) <= 0 { 642 t.Error("Expected average fee from first block to be greater than average fee from second block.") 643 } 644 if totalFee1.Cmp(totalFee2) <= 0 { 645 t.Error("Expected total fee from first block to be greater than total fee from second block.") 646 } 647 if numTxns1 < numTxns2 { 648 t.Error("Expected more transactions in the first block than second block.") 649 } 650 if maxFee1.Cmp(maxFee2) <= 0 { 651 t.Error("Expected highest fee from first block to be greater than highest fee from second block.") 652 } 653 if minFee1.Cmp(maxFee2) < 0 { 654 t.Error("Expected lowest fee from first block to be greater than or equal to than highest fee from second block.") 655 } 656 if maxFee1.Cmp(minFee1) <= 0 { 657 t.Error("Expected highest fee from first block to be greater than lowest fee from first block.") 658 } 659 if maxFee2.Cmp(minFee2) <= 0 { 660 t.Error("Expected highest fee from second block to be greater than lowest fee from second block.") 661 } 662 } 663 664 // TestBigTpool creates 3 chunks of 10,000 transactions such that the second 665 // chunk has much greater fees than the first chunk, and the third chunk has 666 // much greater fees than the second chunk. The fees in each chunk are also 667 // increasing. Then, to induce move lots of movement of transactions in and out 668 // of the miner's unsolved block, we make the tester accept the transactions by 669 // interleaving subsets of the chunks. We then check that the miner produces the 670 // expected sequence of blocks. 671 func TestBigTpool(t *testing.T) { 672 if testing.Short() || !build.VLONG { 673 t.SkipNow() 674 } 675 tpt, err := createTpoolTester(t.Name()) 676 if err != nil { 677 t.Fatal(err) 678 } 679 defer tpt.Close() 680 681 // Mine a few more blocks to get some extra funding. 682 for i := 0; i < 4; i++ { 683 _, err := tpt.miner.AddBlock() 684 if err != nil { 685 t.Fatal(err) 686 } 687 } 688 689 // Create transaction graph setup. 690 coinFrac := types.SiacoinPrecision.Div64(1) 691 feeFrac := types.SiacoinPrecision.Div64(10) 692 numGraphsPerChunk := 1000 693 transactionSetSizes := []int{1, 2, 5, 10, 20} 694 695 var outputs1 []types.SiacoinOutput 696 var outputs2 []types.SiacoinOutput 697 var outputs3 []types.SiacoinOutput 698 699 // Create outputs to be spent in the first chunk. 700 for i := 1; i <= numGraphsPerChunk; i++ { 701 value := coinFrac.Mul64(25).Add(feeFrac.Mul64(uint64(i))).Mul64(2) 702 outputs1 = append(outputs1, types.SiacoinOutput{ 703 UnlockHash: types.UnlockConditions{}.UnlockHash(), 704 Value: value, 705 }) 706 } 707 // There's so many outputs that they need to be put into multiple 708 // transactions. We can fit around 500 outputs per transaction. 709 var outputTxns1 [][]types.Transaction 710 for i := 0; i < numGraphsPerChunk/500; i++ { 711 txns, err := tpt.wallet.SendSiacoinsMulti(outputs1[500*i : (500*i)+500]) 712 if err != nil { 713 t.Error(err) 714 } 715 outputTxns1 = append(outputTxns1, txns) 716 } 717 718 counter := 1 719 var graphs [][]types.Transaction 720 for _, output := range outputTxns1 { 721 finalTxn := output[len(output)-1] 722 for i := 0; i < 500; i++ { // 500 is the the number of outputs 723 var edges []types.TransactionGraphEdge 724 totalValue := coinFrac.Mul64(25).Add(feeFrac.Mul64(uint64(counter))).Mul64(2) 725 setSize := transactionSetSizes[fastrand.Intn(5)] // 1, 2, 5, 10, or 20 with equal probability 726 txTotalVal := totalValue.Div64(uint64(setSize)) 727 txFee := txTotalVal.Div64(5) 728 txVal := txTotalVal.Sub(txFee) 729 txFee2 := txVal.Div64(2) 730 txVal2 := txVal.Sub(txFee2) 731 732 for i := 0; i < setSize; i++ { 733 edges = append(edges, types.TransactionGraphEdge{ 734 Dest: i + 1, 735 Fee: txFee, 736 Source: 0, 737 Value: txVal, 738 }) 739 } 740 for i := 0; i < setSize; i++ { 741 edges = append(edges, types.TransactionGraphEdge{ 742 Dest: i + 1 + setSize, 743 Fee: txFee2, 744 Source: i + 1, 745 Value: txVal2, 746 }) 747 } 748 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 749 if err != nil { 750 t.Fatal(err) 751 } 752 graphs = append(graphs, graph) 753 counter++ 754 } 755 } 756 //////////////////////////////////////////////////////////////////////////// 757 // Chunk 2 758 //////////////////////////////////////////////////////////////////////////// 759 // Create outputs to be spent in the second chunk. 760 for i := 1; i <= numGraphsPerChunk; i++ { 761 value := coinFrac.Mul64(60).Add(feeFrac.Mul64(uint64(i))).Mul64(2) 762 outputs2 = append(outputs2, types.SiacoinOutput{ 763 UnlockHash: types.UnlockConditions{}.UnlockHash(), 764 Value: value, 765 }) 766 } 767 // There's so many outputs that they need to be put into multiple 768 // transactions. We can fit around 500 outputs per transaction. 769 var outputTxns2 [][]types.Transaction 770 for i := 0; i < numGraphsPerChunk/500; i++ { 771 txns, err := tpt.wallet.SendSiacoinsMulti(outputs2[500*i : (500*i)+500]) 772 if err != nil { 773 t.Error(err) 774 } 775 outputTxns2 = append(outputTxns2, txns) 776 } 777 778 counter = 1 779 var graphs2 [][]types.Transaction 780 for _, output := range outputTxns2 { 781 finalTxn := output[len(output)-1] 782 for i := 0; i < 500; i++ { // 500 is the the number of outputs. 783 var edges []types.TransactionGraphEdge 784 totalValue := coinFrac.Mul64(60).Add(feeFrac.Mul64(uint64(counter))).Mul64(2) 785 setSize := transactionSetSizes[fastrand.Intn(5)] // 1, 2, 5, 10, or 20 with equal probability 786 txTotalVal := totalValue.Div64(uint64(setSize)) 787 txFee := txTotalVal.Div64(5) 788 txVal := txTotalVal.Sub(txFee) 789 txFee2 := txVal.Div64(2) 790 txVal2 := txVal.Sub(txFee2) 791 792 for i := 0; i < setSize; i++ { 793 edges = append(edges, types.TransactionGraphEdge{ 794 Dest: i + 1, 795 Fee: txFee, 796 Source: 0, 797 Value: txVal, 798 }) 799 } 800 for i := 0; i < setSize; i++ { 801 edges = append(edges, types.TransactionGraphEdge{ 802 Dest: i + 1 + setSize, 803 Fee: txFee2, 804 Source: i + 1, 805 Value: txVal2, 806 }) 807 } 808 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 809 if err != nil { 810 t.Fatal(err) 811 } 812 graphs2 = append(graphs2, graph) 813 counter++ 814 } 815 } 816 //////////////////////////////////////////////////////////////////////////// 817 // Chunk 3 818 //////////////////////////////////////////////////////////////////////////// 819 // Create outputs to be spent in the third chunk. 820 for i := 1; i <= numGraphsPerChunk; i++ { 821 value := coinFrac.Mul64(110).Add(feeFrac.Mul64(uint64(i))).Mul64(2) 822 outputs3 = append(outputs3, types.SiacoinOutput{ 823 UnlockHash: types.UnlockConditions{}.UnlockHash(), 824 Value: value, 825 }) 826 } 827 // There's so many outputs that they need to be put into multiple 828 // transactions. We can fit around 500 outputs per transaction. 829 var outputTxns3 [][]types.Transaction 830 for i := 0; i < numGraphsPerChunk/500; i++ { 831 txns, err := tpt.wallet.SendSiacoinsMulti(outputs3[500*i : (500*i)+500]) 832 if err != nil { 833 t.Error(err) 834 } 835 outputTxns3 = append(outputTxns3, txns) 836 } 837 838 counter = 1 839 var graphs3 [][]types.Transaction 840 for _, output := range outputTxns3 { 841 finalTxn := output[len(output)-1] 842 for i := 0; i < 500; i++ { // 500 is the the number of outputs. 843 var edges []types.TransactionGraphEdge 844 totalValue := coinFrac.Mul64(110).Add(feeFrac.Mul64(uint64(counter))).Mul64(2) 845 setSize := transactionSetSizes[fastrand.Intn(5)] // 1, 2, 5, 10, or 20 with equal probability 846 txTotalVal := totalValue.Div64(uint64(setSize)) 847 txFee := txTotalVal.Div64(5) 848 txVal := txTotalVal.Sub(txFee) 849 txFee2 := txVal.Div64(2) 850 txVal2 := txVal.Sub(txFee2) 851 852 for i := 0; i < setSize; i++ { 853 edges = append(edges, types.TransactionGraphEdge{ 854 Dest: i + 1, 855 Fee: txFee, 856 Source: 0, 857 Value: txVal, 858 }) 859 } 860 for i := 0; i < setSize; i++ { 861 edges = append(edges, types.TransactionGraphEdge{ 862 Dest: i + 1 + setSize, 863 Fee: txFee2, 864 Source: i + 1, 865 Value: txVal2, 866 }) 867 } 868 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 869 if err != nil { 870 t.Fatal(err) 871 } 872 graphs3 = append(graphs3, graph) 873 counter++ 874 } 875 } 876 877 block, err := tpt.miner.AddBlock() 878 if err != nil { 879 t.Fatal(err) 880 } 881 882 // Accept the parent node of each graph so that its outputs we can test 883 // spending its outputs after mining the next block. 884 c := 0 885 for _, graph := range graphs { 886 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{graph[0]}) 887 if err != nil { 888 t.Fatal(err) 889 } 890 c++ 891 } 892 _, err = tpt.miner.AddBlock() 893 if err != nil { 894 t.Fatal(err) 895 } 896 for _, graph := range graphs2 { 897 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{graph[0]}) 898 if err != nil { 899 t.Fatal(err) 900 } 901 } 902 _, err = tpt.miner.AddBlock() 903 if err != nil { 904 t.Fatal(err) 905 } 906 for _, graph := range graphs3 { 907 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{graph[0]}) 908 if err != nil { 909 t.Fatal(err) 910 } 911 } 912 _, err = tpt.miner.AddBlock() 913 if err != nil { 914 t.Fatal(err) 915 } 916 917 var totalGraph [][]types.Transaction 918 totalGraph = append(totalGraph, graphs...) 919 totalGraph = append(totalGraph, graphs2...) 920 totalGraph = append(totalGraph, graphs3...) 921 922 // Add transactions one megabyte at a time. 923 firstMix := fastrand.Perm(670) // around 670 graphs make 1MB of transactions 924 secondMix := fastrand.Perm(670) 925 thirdMix := fastrand.Perm(670) 926 fourthMix := fastrand.Perm(670) 927 fifthMix := fastrand.Perm(320) 928 929 for _, i := range firstMix { 930 graph := totalGraph[i] 931 for _, txn := range graph[1:] { 932 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 933 if err != nil { 934 t.Fatal(err) 935 } 936 } 937 } 938 for _, i := range secondMix { 939 graph := totalGraph[i+670] 940 for _, txn := range graph[1:] { 941 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 942 if err != nil { 943 t.Fatal(err) 944 } 945 } 946 } 947 for _, i := range thirdMix { 948 graph := totalGraph[i+670+670] 949 for _, txn := range graph[1:] { 950 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 951 if err != nil { 952 t.Fatal(err) 953 } 954 } 955 } 956 for _, i := range fourthMix { 957 graph := totalGraph[i+670+670+670] 958 for _, txn := range graph[1:] { 959 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 960 if err != nil { 961 t.Fatal(err) 962 } 963 } 964 } 965 for _, i := range fifthMix { 966 graph := totalGraph[i+670+670+670+670] 967 for _, txn := range graph[1:] { 968 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 969 if err != nil { 970 t.Fatal(err) 971 } 972 } 973 } 974 975 block, err = tpt.miner.AddBlock() 976 if err != nil { 977 t.Fatal(err) 978 } 979 var totalFee1 types.Currency 980 minFee1 := types.SiacoinPrecision.Mul64(10000000000) // All the fees are much smaller than 1 SC. 981 for _, tx := range block.Transactions { 982 for _, fee := range tx.MinerFees { 983 totalFee1 = totalFee1.Add(fee) 984 if fee.Cmp(minFee1) < 0 { 985 minFee1 = fee 986 } 987 } 988 } 989 990 block, err = tpt.miner.AddBlock() 991 if err != nil { 992 t.Fatal(err) 993 } 994 var totalFee2 types.Currency 995 maxFee2 := types.ZeroCurrency 996 minFee2 := types.SiacoinPrecision.Mul64(10000000000) // All the fees are much smaller than 1 SC. 997 for _, tx := range block.Transactions { 998 for _, fee := range tx.MinerFees { 999 totalFee2 = totalFee2.Add(fee) 1000 if fee.Cmp(minFee2) < 0 { 1001 minFee2 = fee 1002 } 1003 if fee.Cmp(maxFee2) > 0 { 1004 maxFee2 = fee 1005 } 1006 } 1007 } 1008 1009 block, err = tpt.miner.AddBlock() 1010 if err != nil { 1011 t.Fatal(err) 1012 } 1013 var totalFee3 types.Currency 1014 maxFee3 := types.ZeroCurrency 1015 minFee3 := types.SiacoinPrecision.Mul64(10000000000) // All the fees are much smaller than 1 SC. 1016 for _, tx := range block.Transactions { 1017 for _, fee := range tx.MinerFees { 1018 totalFee3 = totalFee3.Add(fee) 1019 if fee.Cmp(minFee3) < 0 { 1020 minFee3 = fee 1021 } 1022 if fee.Cmp(maxFee3) > 0 { 1023 maxFee3 = fee 1024 } 1025 } 1026 } 1027 1028 block, err = tpt.miner.AddBlock() 1029 if err != nil { 1030 t.Fatal(err) 1031 } 1032 totalFee4 := types.ZeroCurrency 1033 maxFee4 := types.ZeroCurrency 1034 for _, tx := range block.Transactions { 1035 for _, fee := range tx.MinerFees { 1036 totalFee4 = totalFee4.Add(fee) 1037 if fee.Cmp(maxFee4) > 0 { 1038 maxFee4 = fee 1039 } 1040 } 1041 } 1042 1043 // Check that the total fees from each block are decreasing. 1044 if totalFee1.Cmp(totalFee2) < 0 { 1045 t.Error("Expected fees from the first block to be greater than from the second block.") 1046 } 1047 if totalFee2.Cmp(totalFee3) < 0 { 1048 t.Error("Expected fees from the second block to be greater than from the third block.") 1049 } 1050 if totalFee3.Cmp(totalFee4) < 0 { 1051 t.Error("Expected fees from the third block to be greater than from the fourth block.") 1052 } 1053 // Check that the min fees from each block is greater than or equal to the max fee for 1054 // the block mined after it. 1055 if minFee1.Cmp(maxFee2) < 0 { 1056 t.Error("Expected min fee from the first block to be greater than the max fee from the second block.") 1057 } 1058 if minFee2.Cmp(maxFee3) < 0 { 1059 t.Error("Expected min fee from the second block to be greater than the max fee from the third block.") 1060 } 1061 if minFee3.Cmp(maxFee4) < 0 { 1062 t.Error("Expected min fee from the third block to be greater than the max fee from the fourth block.") 1063 } 1064 } 1065 1066 // TestTpoolRevert tests proper transaction pool reverts. In this test we create 1067 // two testers who set up the same outputs for a group of transaction sets. Then 1068 // one accepts a small subset of those sets, mines a block, and passes that 1069 // block to the other tester. Then that tester mines two blocks. We check that 1070 // the total fee for all 3 blocks is as expected, and that the last block only 1071 // has transactions with fees less than those in the block prior. 1072 func TestTpoolRevert(t *testing.T) { 1073 if testing.Short() || !build.VLONG { 1074 t.SkipNow() 1075 } 1076 1077 tpt, err := blankTpoolTester(t.Name()) 1078 if err != nil { 1079 t.Fatal(err) 1080 } 1081 tpt2, err := blankTpoolTester(t.Name() + "2") 1082 if err != nil { 1083 t.Fatal(err) 1084 } 1085 defer tpt.Close() 1086 defer tpt2.Close() 1087 1088 // Mine blocks until there is money in the wallet. We have to make sure they 1089 // are on the same chain by feeding all blocks to the other tester. 1090 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 1091 b, _ := tpt.miner.FindBlock() 1092 err = tpt.cs.AcceptBlock(b) 1093 if err != nil { 1094 t.Fatal(err) 1095 } 1096 err = tpt2.cs.AcceptBlock(b) 1097 if err != nil { 1098 t.Fatal(err) 1099 } 1100 } 1101 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 1102 b, _ := tpt2.miner.FindBlock() 1103 err = tpt2.cs.AcceptBlock(b) 1104 if err != nil { 1105 t.Fatal(err) 1106 } 1107 err = tpt.cs.AcceptBlock(b) 1108 if err != nil { 1109 t.Fatal(err) 1110 } 1111 } 1112 1113 // Mine a few more blocks to get some extra funding. 1114 for i := 0; i < 4; i++ { 1115 block, err := tpt.miner.AddBlock() 1116 if err != nil { 1117 t.Fatal(err) 1118 } 1119 err = tpt2.cs.AcceptBlock(block) 1120 if err != nil { 1121 t.Fatal(err) 1122 } 1123 } 1124 1125 // Create transaction graph setup. 1126 coinFrac := types.SiacoinPrecision 1127 numGraphs := 110 1128 graphFund := coinFrac.Mul64(12210) 1129 var outputs []types.SiacoinOutput 1130 for i := 0; i < numGraphs; i++ { 1131 outputs = append(outputs, types.SiacoinOutput{ 1132 UnlockHash: types.UnlockConditions{}.UnlockHash(), 1133 Value: graphFund, 1134 }) 1135 } 1136 txns, err := tpt.wallet.SendSiacoinsMulti(outputs) 1137 if err != nil { 1138 t.Error(err) 1139 } 1140 // Mine the graph setup in the consensus set so that the graph outputs are 1141 // transaction sets. This guarantees that the parent of every graph will be 1142 // its own output. 1143 block, err := tpt.miner.AddBlock() 1144 if err != nil { 1145 t.Fatal(err) 1146 } 1147 err = tpt2.cs.AcceptBlock(block) 1148 if err != nil { 1149 t.Fatal(err) 1150 } 1151 1152 finalTxn := txns[len(txns)-1] 1153 // For each output, create 250 transactions 1154 var graphs [][]types.Transaction 1155 for i := 0; i < numGraphs; i++ { 1156 var edges []types.TransactionGraphEdge 1157 var cumFee types.Currency 1158 for j := 0; j < numGraphs; j++ { 1159 fee := coinFrac.Mul64(uint64((j + 1))) 1160 cumFee = cumFee.Add(fee) 1161 edges = append(edges, types.TransactionGraphEdge{ 1162 Dest: j + 1, 1163 Fee: fee, 1164 Source: 0, 1165 Value: fee, 1166 }) 1167 } 1168 for k := 0; k < numGraphs; k++ { 1169 fee := coinFrac.Mul64(uint64(k + 1)).Div64(2) 1170 cumFee = cumFee.Add(fee) 1171 edges = append(edges, types.TransactionGraphEdge{ 1172 Dest: k + 251, 1173 Fee: fee, 1174 Source: k + 1, 1175 Value: fee, 1176 }) 1177 } 1178 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 1179 if err != nil { 1180 t.Fatal(err) 1181 } 1182 graphs = append(graphs, graph) 1183 1184 } 1185 // Accept the parent node of each graph so that its outputs we can test 1186 // spending its outputs after mining the next block. 1187 for _, graph := range graphs { 1188 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{graph[0]}) 1189 if err != nil { 1190 t.Fatal(err) 1191 } 1192 err = tpt2.tpool.AcceptTransactionSet([]types.Transaction{graph[0]}) 1193 if err != nil { 1194 t.Fatal(err) 1195 } 1196 } 1197 block, err = tpt.miner.AddBlock() 1198 if err != nil { 1199 t.Fatal(err) 1200 } 1201 err = tpt2.cs.AcceptBlock(block) 1202 if err != nil { 1203 t.Fatal(err) 1204 } 1205 1206 // Now accept all the other nodes of each graph. 1207 for _, graph := range graphs { 1208 for _, txn := range graph[1:] { 1209 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 1210 if err != nil { 1211 t.Fatal(err) 1212 } 1213 } 1214 } 1215 // Accept a randomly selected subset of transactions with the other tester. 1216 randSet := fastrand.Perm(len(graphs)) 1217 for i := 0; i < len(randSet)/10; i++ { 1218 graph := graphs[randSet[i]] 1219 for _, txn := range graph[1:] { 1220 err := tpt2.tpool.AcceptTransactionSet([]types.Transaction{txn}) 1221 if err != nil { 1222 t.Fatal(err) 1223 } 1224 } 1225 } 1226 1227 // Now the second tester mines the random subset, and gives the block to the 1228 // first tester. 1229 block, err = tpt2.miner.AddBlock() 1230 if err != nil { 1231 t.Fatal(err) 1232 } 1233 err = tpt.cs.AcceptBlock(block) 1234 if err != nil { 1235 t.Fatal(err) 1236 } 1237 1238 //Now we add up all the fees from this first block. 1239 var totalFeeRandBlock types.Currency 1240 for _, tx := range block.Transactions { 1241 for _, fee := range tx.MinerFees { 1242 totalFeeRandBlock = totalFeeRandBlock.Add(fee) 1243 } 1244 } 1245 1246 // Mine the next block so we can check the transactions inside 1247 block, err = tpt.miner.AddBlock() 1248 if err != nil { 1249 t.Fatal(err) 1250 } 1251 var totalFee1 types.Currency 1252 maxFee1 := types.SiacoinPrecision.Div64(1000000) 1253 minFee1 := types.SiacoinPrecision.Mul64(1000000) 1254 for _, txn := range block.Transactions { 1255 for _, fee := range txn.MinerFees { 1256 if fee.Cmp(maxFee1) >= 0 { 1257 maxFee1 = fee 1258 } 1259 if fee.Cmp(minFee1) <= 0 { 1260 minFee1 = fee 1261 } 1262 totalFee1 = totalFee1.Add(fee) 1263 } 1264 } 1265 1266 // Mine the next block so we can check the transactions inside 1267 block, err = tpt.miner.AddBlock() 1268 if err != nil { 1269 t.Fatal(err) 1270 } 1271 var totalFee2 types.Currency 1272 maxFee2 := types.SiacoinPrecision.Div64(1000000) 1273 minFee2 := types.SiacoinPrecision.Mul64(1000000) 1274 for _, txn := range block.Transactions { 1275 for _, fee := range txn.MinerFees { 1276 if fee.Cmp(maxFee2) >= 0 { 1277 maxFee2 = fee 1278 } 1279 if fee.Cmp(minFee2) <= 0 { 1280 minFee2 = fee 1281 } 1282 totalFee2 = totalFee2.Add(fee) 1283 } 1284 } 1285 1286 totalFeeAcrossBlocks := totalFeeRandBlock.Add(totalFee1).Add(totalFee2) 1287 totalExpectedFee := types.SiacoinPrecision.Mul64(13860).Add(types.SiacoinPrecision.Mul64(321915)) 1288 if totalFeeAcrossBlocks.Cmp(totalExpectedFee) != 0 { 1289 t.Error("Fee different from expected.") 1290 } 1291 if maxFee1.Cmp(maxFee2) <= 0 { 1292 t.Error("Expected highest fee from first block to be greater than highest fee from second block.") 1293 } 1294 if minFee1.Cmp(maxFee2) < 0 { 1295 t.Error("Expected lowest fee from first block to be greater than or equal to than highest fee from second block.") 1296 } 1297 if maxFee1.Cmp(minFee1) < 0 { 1298 t.Error("Expected highest fee from first block to be greater than lowest fee from first block.") 1299 } 1300 if maxFee2.Cmp(minFee2) < 0 { 1301 t.Error("Expected highest fee from second block to be greater than lowest fee from second block.") 1302 } 1303 }