github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/transactionpool/accept_test.go (about) 1 package transactionpool 2 3 import ( 4 "testing" 5 6 "github.com/Synthesix/Sia/modules" 7 "github.com/Synthesix/Sia/types" 8 "github.com/NebulousLabs/fastrand" 9 ) 10 11 // TestAcceptTransactionSet probes the AcceptTransactionSet method 12 // of the transaction pool. 13 func TestAcceptTransactionSet(t *testing.T) { 14 if testing.Short() { 15 t.SkipNow() 16 } 17 // Create a transaction pool tester. 18 tpt, err := createTpoolTester(t.Name()) 19 if err != nil { 20 t.Fatal(err) 21 } 22 defer tpt.Close() 23 24 // Check that the transaction pool is empty. 25 if len(tpt.tpool.transactionSets) != 0 { 26 t.Error("transaction pool is not empty") 27 } 28 29 // Create a valid transaction set using the wallet. 30 txns, err := tpt.wallet.SendSiacoins(types.NewCurrency64(100), types.UnlockHash{}) 31 if err != nil { 32 t.Fatal(err) 33 } 34 if len(tpt.tpool.transactionSets) != 1 { 35 t.Error("sending coins did not increase the transaction sets by 1") 36 } 37 38 // Submit the transaction set again to trigger a duplication error. 39 err = tpt.tpool.AcceptTransactionSet(txns) 40 if err != modules.ErrDuplicateTransactionSet { 41 t.Error(err) 42 } 43 44 // Mine a block and check that the transaction pool gets emptied. 45 block, _ := tpt.miner.FindBlock() 46 err = tpt.cs.AcceptBlock(block) 47 if err != nil { 48 t.Fatal(err) 49 } 50 if len(tpt.tpool.TransactionList()) != 0 { 51 t.Error("transaction pool was not emptied after mining a block") 52 } 53 54 // Try to resubmit the transaction set to verify 55 err = tpt.tpool.AcceptTransactionSet(txns) 56 if err == nil { 57 t.Error("transaction set was supposed to be rejected") 58 } 59 } 60 61 // TestConflictingTransactionSets tries to add two transaction sets 62 // to the transaction pool that are each legal individually, but double spend 63 // an output. 64 func TestConflictingTransactionSets(t *testing.T) { 65 if testing.Short() { 66 t.SkipNow() 67 } 68 // Create a transaction pool tester. 69 tpt, err := createTpoolTester(t.Name()) 70 if err != nil { 71 t.Fatal(err) 72 } 73 defer tpt.Close() 74 75 // Fund a partial transaction. 76 fund := types.NewCurrency64(30e6) 77 txnBuilder := tpt.wallet.StartTransaction() 78 err = txnBuilder.FundSiacoins(fund) 79 if err != nil { 80 t.Fatal(err) 81 } 82 // wholeTransaction is set to false so that we can use the same signature 83 // to create a double spend. 84 txnSet, err := txnBuilder.Sign(false) 85 if err != nil { 86 t.Fatal(err) 87 } 88 txnSetDoubleSpend := make([]types.Transaction, len(txnSet)) 89 copy(txnSetDoubleSpend, txnSet) 90 91 // There are now two sets of transactions that are signed and ready to 92 // spend the same output. Have one spend the money in a miner fee, and the 93 // other create a siacoin output. 94 txnIndex := len(txnSet) - 1 95 txnSet[txnIndex].MinerFees = append(txnSet[txnIndex].MinerFees, fund) 96 txnSetDoubleSpend[txnIndex].SiacoinOutputs = append(txnSetDoubleSpend[txnIndex].SiacoinOutputs, types.SiacoinOutput{Value: fund}) 97 98 // Add the first and then the second txn set. 99 err = tpt.tpool.AcceptTransactionSet(txnSet) 100 if err != nil { 101 t.Error(err) 102 } 103 err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend) 104 if err == nil { 105 t.Error("transaction should not have passed inspection") 106 } 107 108 // Purge and try the sets in the reverse order. 109 tpt.tpool.PurgeTransactionPool() 110 err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend) 111 if err != nil { 112 t.Error(err) 113 } 114 err = tpt.tpool.AcceptTransactionSet(txnSet) 115 if err == nil { 116 t.Error("transaction should not have passed inspection") 117 } 118 } 119 120 // TestCheckMinerFees probes the checkMinerFees method of the 121 // transaction pool. 122 func TestCheckMinerFees(t *testing.T) { 123 if testing.Short() { 124 t.SkipNow() 125 } 126 // Create a transaction pool tester. 127 tpt, err := createTpoolTester(t.Name()) 128 if err != nil { 129 t.Fatal(err) 130 } 131 defer tpt.Close() 132 133 // Prepare a bunch of outputs for a series of graphs to fill up the 134 // transaction pool. 135 graphLens := 200 // 40 kb per graph 136 numGraphs := (int(TransactionPoolSizeTarget) * 4 / 3) / (graphLens * 206) // 206 is the size of a single input-output graph txn. 137 graphFund := types.SiacoinPrecision.Mul64(1000) 138 var outputs []types.SiacoinOutput 139 for i := 0; i < numGraphs+1; i++ { 140 outputs = append(outputs, types.SiacoinOutput{ 141 UnlockHash: types.UnlockConditions{}.UnlockHash(), 142 Value: graphFund, 143 }) 144 } 145 txns, err := tpt.wallet.SendSiacoinsMulti(outputs) 146 if err != nil { 147 t.Error(err) 148 } 149 150 // Mine the graph setup in the consensus set so that the graph outputs are 151 // distinct transaction sets. 152 _, err = tpt.miner.AddBlock() 153 if err != nil { 154 t.Fatal(err) 155 } 156 157 // Recommended fees at this point should be the minimum. 158 minRec, maxRec := tpt.tpool.FeeEstimation() 159 if minRec.Cmp(minEstimation) < 0 { 160 t.Error("transaction pool is not respecting the sane fee minimum") 161 } 162 if maxRec.Cmp(minEstimation.Mul64(3)) < 0 { 163 t.Error("transaction pool is not respecting the sane fee min maximum") 164 } 165 166 // Fill the transaction pool to the fee limit. 167 for i := 0; i < TransactionPoolSizeForFee/10e3; i++ { 168 arbData := make([]byte, 10e3) 169 copy(arbData, modules.PrefixNonSia[:]) 170 fastrand.Read(arbData[100:116]) // prevents collisions with other transacitons in the loop. 171 txn := types.Transaction{ArbitraryData: [][]byte{arbData}} 172 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 173 if err != nil { 174 t.Fatal(err) 175 } 176 } 177 178 // Add another transaction, this one should fail for having too few fees. 179 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{{}}) 180 if err != errLowMinerFees { 181 t.Error(err) 182 } 183 184 // Add a transaction that has sufficient fees. 185 _, err = tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(50), types.UnlockHash{}) 186 if err != nil { 187 t.Fatal(err) 188 } 189 190 // Create all of the graphs. 191 finalTxn := txns[len(txns)-1] 192 for i := 0; i < numGraphs; i++ { 193 var edges []types.TransactionGraphEdge 194 for j := 0; j < graphLens; j++ { 195 edges = append(edges, types.TransactionGraphEdge{ 196 Dest: j + 1, 197 Fee: types.SiacoinPrecision, 198 Source: j, 199 Value: graphFund.Sub(types.SiacoinPrecision.Mul64(uint64(j + 1))), 200 }) 201 } 202 graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges) 203 if err != nil { 204 t.Fatal(err) 205 } 206 err = tpt.tpool.AcceptTransactionSet(graph) 207 if err != nil { 208 t.Fatal(err) 209 } 210 } 211 212 // Try to submit a transaction with too few fees. 213 source := finalTxn.SiacoinOutputID(uint64(numGraphs)) 214 lowFee := types.SiacoinPrecision.Div64(3) 215 remaining := types.SiacoinPrecision.Mul64(1000).Sub(lowFee) 216 edge := types.TransactionGraphEdge{ 217 Dest: 1, 218 Fee: lowFee, 219 Source: 0, 220 Value: remaining, 221 } 222 lowFeeGraph, err := types.TransactionGraph(source, []types.TransactionGraphEdge{edge}) 223 if err != nil { 224 t.Fatal(err) 225 } 226 err = tpt.tpool.AcceptTransactionSet(lowFeeGraph) 227 if err != errLowMinerFees { 228 t.Fatal(err) 229 } 230 } 231 232 // TestTransactionGraph checks that the TransactionGraph method of the types 233 // package is able to create transasctions that actually validate and can get 234 // inserted into the tpool. 235 func TestTransactionGraph(t *testing.T) { 236 if testing.Short() { 237 t.SkipNow() 238 } 239 // Create a transaction pool tester. 240 tpt, err := createTpoolTester(t.Name()) 241 if err != nil { 242 t.Fatal(err) 243 } 244 defer tpt.Close() 245 246 // Create a transaction sending money to an output that TransactionGraph can 247 // spent (the empty UnlockConditions). 248 txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(100), types.UnlockConditions{}.UnlockHash()) 249 if err != nil { 250 t.Fatal(err) 251 } 252 253 // Get the output of that transaction. 254 graphSourceOutputID := txns[len(txns)-1].SiacoinOutputID(0) 255 edge := types.TransactionGraphEdge{ 256 Dest: 1, 257 Fee: types.SiacoinPrecision.Mul64(10), 258 Source: 0, 259 Value: types.SiacoinPrecision.Mul64(90), 260 } 261 graphTxns, err := types.TransactionGraph(graphSourceOutputID, []types.TransactionGraphEdge{edge}) 262 if err != nil { 263 t.Fatal(err) 264 } 265 if len(graphTxns) != 1 { 266 t.Fatal("wrong number of tranasctions produced") 267 } 268 err = tpt.tpool.AcceptTransactionSet(graphTxns) 269 if err != nil { 270 t.Fatal(err) 271 } 272 } 273 274 // TestTransactionGraphDiamond checks that the TransactionGraph method of the 275 // types package is able to create transasctions that actually validate and can 276 // get inserted into the tpool. 277 func TestTransactionGraphDiamond(t *testing.T) { 278 if testing.Short() { 279 t.SkipNow() 280 } 281 // Create a transaction pool tester. 282 tpt, err := createTpoolTester(t.Name()) 283 if err != nil { 284 t.Fatal(err) 285 } 286 defer tpt.Close() 287 288 // Create a transaction sending money to an output that TransactionGraph can 289 // spent (the empty UnlockConditions). 290 txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(100), types.UnlockConditions{}.UnlockHash()) 291 if err != nil { 292 t.Fatal(err) 293 } 294 295 // Get the output of that transaction. 296 graphSourceOutputID := txns[len(txns)-1].SiacoinOutputID(0) 297 var edges []types.TransactionGraphEdge 298 sources := []int{0, 0, 1, 2} 299 dests := []int{1, 2, 3, 3} 300 values := []uint64{40, 40, 30, 30} 301 fees := []uint64{10, 10, 10, 10} 302 for i := range sources { 303 edges = append(edges, types.TransactionGraphEdge{ 304 Dest: dests[i], 305 Fee: types.SiacoinPrecision.Mul64(fees[i]), 306 Source: sources[i], 307 Value: types.SiacoinPrecision.Mul64(values[i]), 308 }) 309 } 310 graphTxns, err := types.TransactionGraph(graphSourceOutputID, edges) 311 if err != nil { 312 t.Fatal(err) 313 } 314 if len(graphTxns) != 3 { 315 t.Fatal("wrong number of tranasctions produced") 316 } 317 err = tpt.tpool.AcceptTransactionSet(graphTxns) 318 if err != nil { 319 t.Fatal(err) 320 } 321 } 322 323 // TestTransactionSuperset submits a single transaction to the network, 324 // followed by a transaction set containing that single transaction. 325 func TestTransactionSuperset(t *testing.T) { 326 if testing.Short() { 327 t.SkipNow() 328 } 329 // Create a transaction pool tester. 330 tpt, err := createTpoolTester(t.Name()) 331 if err != nil { 332 t.Fatal(err) 333 } 334 defer tpt.Close() 335 336 // Fund a partial transaction. 337 fund := types.NewCurrency64(30e6) 338 txnBuilder := tpt.wallet.StartTransaction() 339 err = txnBuilder.FundSiacoins(fund) 340 if err != nil { 341 t.Fatal(err) 342 } 343 txnBuilder.AddMinerFee(fund) 344 // wholeTransaction is set to false so that we can use the same signature 345 // to create a double spend. 346 txnSet, err := txnBuilder.Sign(false) 347 if err != nil { 348 t.Fatal(err) 349 } 350 if len(txnSet) <= 1 { 351 t.Fatal("test is invalid unless the transaction set has two or more transactions") 352 } 353 // Check that the second transaction is dependent on the first. 354 err = tpt.tpool.AcceptTransactionSet(txnSet[1:]) 355 if err == nil { 356 t.Fatal("transaction set must have dependent transactions") 357 } 358 359 // Submit the first transaction in the set to the transaction pool, and 360 // then the superset. 361 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 362 if err != nil { 363 t.Fatal("first transaction in the transaction set was not valid?") 364 } 365 err = tpt.tpool.AcceptTransactionSet(txnSet) 366 if err != nil { 367 t.Fatal("super setting is not working:", err) 368 } 369 370 // Try resubmitting the individual transaction and the superset, a 371 // duplication error should be returned for each case. 372 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 373 if err != modules.ErrDuplicateTransactionSet { 374 t.Fatal(err) 375 } 376 err = tpt.tpool.AcceptTransactionSet(txnSet) 377 if err != modules.ErrDuplicateTransactionSet { 378 t.Fatal("super setting is not working:", err) 379 } 380 } 381 382 // TestTransactionSubset submits a transaction set to the network, followed by 383 // just a subset, expectint ErrDuplicateTransactionSet as a response. 384 func TestTransactionSubset(t *testing.T) { 385 if testing.Short() { 386 t.SkipNow() 387 } 388 // Create a transaction pool tester. 389 tpt, err := createTpoolTester(t.Name()) 390 if err != nil { 391 t.Fatal(err) 392 } 393 defer tpt.Close() 394 395 // Fund a partial transaction. 396 fund := types.NewCurrency64(30e6) 397 txnBuilder := tpt.wallet.StartTransaction() 398 err = txnBuilder.FundSiacoins(fund) 399 if err != nil { 400 t.Fatal(err) 401 } 402 txnBuilder.AddMinerFee(fund) 403 // wholeTransaction is set to false so that we can use the same signature 404 // to create a double spend. 405 txnSet, err := txnBuilder.Sign(false) 406 if err != nil { 407 t.Fatal(err) 408 } 409 if len(txnSet) <= 1 { 410 t.Fatal("test is invalid unless the transaction set has two or more transactions") 411 } 412 // Check that the second transaction is dependent on the first. 413 err = tpt.tpool.AcceptTransactionSet(txnSet[1:]) 414 if err == nil { 415 t.Fatal("transaction set must have dependent transactions") 416 } 417 418 // Submit the set to the pool, followed by just the transaction. 419 err = tpt.tpool.AcceptTransactionSet(txnSet) 420 if err != nil { 421 t.Fatal("super setting is not working:", err) 422 } 423 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 424 if err != modules.ErrDuplicateTransactionSet { 425 t.Fatal(err) 426 } 427 } 428 429 // TestTransactionChild submits a single transaction to the network, 430 // followed by a child transaction. 431 func TestTransactionChild(t *testing.T) { 432 if testing.Short() { 433 t.SkipNow() 434 } 435 // Create a transaction pool tester. 436 tpt, err := createTpoolTester(t.Name()) 437 if err != nil { 438 t.Fatal(err) 439 } 440 defer tpt.Close() 441 442 // Fund a partial transaction. 443 fund := types.NewCurrency64(30e6) 444 txnBuilder := tpt.wallet.StartTransaction() 445 err = txnBuilder.FundSiacoins(fund) 446 if err != nil { 447 t.Fatal(err) 448 } 449 txnBuilder.AddMinerFee(fund) 450 // wholeTransaction is set to false so that we can use the same signature 451 // to create a double spend. 452 txnSet, err := txnBuilder.Sign(false) 453 if err != nil { 454 t.Fatal(err) 455 } 456 if len(txnSet) <= 1 { 457 t.Fatal("test is invalid unless the transaction set has two or more transactions") 458 } 459 // Check that the second transaction is dependent on the first. 460 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txnSet[1]}) 461 if err == nil { 462 t.Fatal("transaction set must have dependent transactions") 463 } 464 465 // Submit the first transaction in the set to the transaction pool. 466 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 467 if err != nil { 468 t.Fatal("first transaction in the transaction set was not valid?") 469 } 470 err = tpt.tpool.AcceptTransactionSet(txnSet[1:]) 471 if err != nil { 472 t.Fatal("child transaction not seen as valid") 473 } 474 } 475 476 // TestNilAccept tries submitting a nil transaction set and a 0-len 477 // transaction set to the transaction pool. 478 func TestNilAccept(t *testing.T) { 479 if testing.Short() { 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 err = tpt.tpool.AcceptTransactionSet(nil) 489 if err == nil { 490 t.Error("no error returned when submitting nothing to the transaction pool") 491 } 492 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{}) 493 if err == nil { 494 t.Error("no error returned when submitting nothing to the transaction pool") 495 } 496 } 497 498 // TestAcceptFCAndConflictingRevision checks that the transaction pool 499 // correctly accepts a file contract in a transaction set followed by a correct 500 // revision to that file contract in the a following transaction set, with no 501 // block separating them. 502 func TestAcceptFCAndConflictingRevision(t *testing.T) { 503 if testing.Short() { 504 t.SkipNow() 505 } 506 tpt, err := createTpoolTester(t.Name()) 507 if err != nil { 508 t.Fatal(err) 509 } 510 defer tpt.Close() 511 512 // Create and fund a valid file contract. 513 builder := tpt.wallet.StartTransaction() 514 payout := types.NewCurrency64(1e9) 515 err = builder.FundSiacoins(payout) 516 if err != nil { 517 t.Fatal(err) 518 } 519 builder.AddFileContract(types.FileContract{ 520 WindowStart: tpt.cs.Height() + 2, 521 WindowEnd: tpt.cs.Height() + 5, 522 Payout: payout, 523 ValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 524 MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 525 UnlockHash: types.UnlockConditions{}.UnlockHash(), 526 }) 527 tSet, err := builder.Sign(true) 528 if err != nil { 529 t.Fatal(err) 530 } 531 err = tpt.tpool.AcceptTransactionSet(tSet) 532 if err != nil { 533 t.Fatal(err) 534 } 535 fcid := tSet[len(tSet)-1].FileContractID(0) 536 537 // Create a file contract revision and submit it. 538 rSet := []types.Transaction{{ 539 FileContractRevisions: []types.FileContractRevision{{ 540 ParentID: fcid, 541 NewRevisionNumber: 2, 542 543 NewWindowStart: tpt.cs.Height() + 2, 544 NewWindowEnd: tpt.cs.Height() + 5, 545 NewValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 546 NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 547 }}, 548 }} 549 err = tpt.tpool.AcceptTransactionSet(rSet) 550 if err != nil { 551 t.Fatal(err) 552 } 553 } 554 555 // TestPartialConfirmation checks that the transaction pool correctly accepts a 556 // transaction set which has parents that have been accepted by the consensus 557 // set but not the whole set has been accepted by the consensus set. 558 func TestPartialConfirmation(t *testing.T) { 559 if testing.Short() { 560 t.SkipNow() 561 } 562 tpt, err := createTpoolTester(t.Name()) 563 if err != nil { 564 t.Fatal(err) 565 } 566 defer tpt.Close() 567 568 // Create and fund a valid file contract. 569 builder := tpt.wallet.StartTransaction() 570 payout := types.NewCurrency64(1e9) 571 err = builder.FundSiacoins(payout) 572 if err != nil { 573 t.Fatal(err) 574 } 575 builder.AddFileContract(types.FileContract{ 576 WindowStart: tpt.cs.Height() + 2, 577 WindowEnd: tpt.cs.Height() + 5, 578 Payout: payout, 579 ValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 580 MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 581 UnlockHash: types.UnlockConditions{}.UnlockHash(), 582 }) 583 tSet, err := builder.Sign(true) 584 if err != nil { 585 t.Fatal(err) 586 } 587 fcid := tSet[len(tSet)-1].FileContractID(0) 588 589 // Create a file contract revision. 590 rSet := []types.Transaction{{ 591 FileContractRevisions: []types.FileContractRevision{{ 592 ParentID: fcid, 593 NewRevisionNumber: 2, 594 595 NewWindowStart: tpt.cs.Height() + 2, 596 NewWindowEnd: tpt.cs.Height() + 5, 597 NewValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 598 NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 599 }}, 600 }} 601 602 // Combine the contract and revision in to a single set. 603 fullSet := append(tSet, rSet...) 604 605 // Get the tSet onto the blockchain. 606 unsolvedBlock, target, err := tpt.miner.BlockForWork() 607 if err != nil { 608 t.Fatal(err) 609 } 610 unsolvedBlock.Transactions = append(unsolvedBlock.Transactions, tSet...) 611 solvedBlock, solved := tpt.miner.SolveBlock(unsolvedBlock, target) 612 if !solved { 613 t.Fatal("Failed to solve block") 614 } 615 err = tpt.cs.AcceptBlock(solvedBlock) 616 if err != nil { 617 t.Fatal(err) 618 } 619 620 // Try to get the full set into the transaction pool. The transaction pool 621 // should recognize that the set is partially accepted, and be able to 622 // accept on the the transactions that are new and are not yet on the 623 // blockchain. 624 err = tpt.tpool.AcceptTransactionSet(fullSet) 625 if err != nil { 626 t.Fatal(err) 627 } 628 } 629 630 // TestPartialConfirmationWeave checks that the transaction pool correctly 631 // accepts a transaction set which has parents that have been accepted by the 632 // consensus set but not the whole set has been accepted by the consensus set, 633 // this time weaving the dependencies, such that the first transaction is not 634 // in the consensus set, the second is, and the third has both as dependencies. 635 func TestPartialConfirmationWeave(t *testing.T) { 636 if testing.Short() { 637 t.SkipNow() 638 } 639 tpt, err := createTpoolTester(t.Name()) 640 if err != nil { 641 t.Fatal(err) 642 } 643 defer tpt.Close() 644 645 // Create a transaction with a single output to a fully controlled address. 646 emptyUH := types.UnlockConditions{}.UnlockHash() 647 builder1 := tpt.wallet.StartTransaction() 648 funding1 := types.NewCurrency64(1e9) 649 err = builder1.FundSiacoins(funding1) 650 if err != nil { 651 t.Fatal(err) 652 } 653 scOutput1 := types.SiacoinOutput{ 654 Value: funding1, 655 UnlockHash: emptyUH, 656 } 657 i1 := builder1.AddSiacoinOutput(scOutput1) 658 tSet1, err := builder1.Sign(true) 659 if err != nil { 660 t.Fatal(err) 661 } 662 // Submit to the transaction pool and mine the block, to minimize 663 // complexity. 664 err = tpt.tpool.AcceptTransactionSet(tSet1) 665 if err != nil { 666 t.Fatal(err) 667 } 668 _, err = tpt.miner.AddBlock() 669 if err != nil { 670 t.Fatal(err) 671 } 672 673 // Create a second output to the fully controlled address, to fund the 674 // second transaction in the weave. 675 builder2 := tpt.wallet.StartTransaction() 676 funding2 := types.NewCurrency64(2e9) 677 err = builder2.FundSiacoins(funding2) 678 if err != nil { 679 t.Fatal(err) 680 } 681 scOutput2 := types.SiacoinOutput{ 682 Value: funding2, 683 UnlockHash: emptyUH, 684 } 685 i2 := builder2.AddSiacoinOutput(scOutput2) 686 tSet2, err := builder2.Sign(true) 687 if err != nil { 688 t.Fatal(err) 689 } 690 // Submit to the transaction pool and mine the block, to minimize 691 // complexity. 692 err = tpt.tpool.AcceptTransactionSet(tSet2) 693 if err != nil { 694 t.Fatal(err) 695 } 696 _, err = tpt.miner.AddBlock() 697 if err != nil { 698 t.Fatal(err) 699 } 700 701 // Create a passthrough transaction for output1 and output2, so that they 702 // can be used as unconfirmed dependencies. 703 txn1 := types.Transaction{ 704 SiacoinInputs: []types.SiacoinInput{{ 705 ParentID: tSet1[len(tSet1)-1].SiacoinOutputID(i1), 706 }}, 707 SiacoinOutputs: []types.SiacoinOutput{{ 708 Value: funding1, 709 UnlockHash: emptyUH, 710 }}, 711 } 712 txn2 := types.Transaction{ 713 SiacoinInputs: []types.SiacoinInput{{ 714 ParentID: tSet2[len(tSet2)-1].SiacoinOutputID(i2), 715 }}, 716 SiacoinOutputs: []types.SiacoinOutput{{ 717 Value: funding2, 718 UnlockHash: emptyUH, 719 }}, 720 } 721 722 // Create a child transaction that depends on inputs from both txn1 and 723 // txn2. 724 child := types.Transaction{ 725 SiacoinInputs: []types.SiacoinInput{ 726 { 727 ParentID: txn1.SiacoinOutputID(0), 728 }, 729 { 730 ParentID: txn2.SiacoinOutputID(0), 731 }, 732 }, 733 SiacoinOutputs: []types.SiacoinOutput{{ 734 Value: funding1.Add(funding2), 735 }}, 736 } 737 738 // Get txn2 accepted into the consensus set. 739 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn2}) 740 if err != nil { 741 t.Fatal(err) 742 } 743 _, err = tpt.miner.AddBlock() 744 if err != nil { 745 t.Fatal(err) 746 } 747 748 // Try to get the set of txn1, txn2, and child accepted into the 749 // transaction pool. 750 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn1, txn2, child}) 751 if err != nil { 752 t.Fatal(err) 753 } 754 }