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