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