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