gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/transactionbuilder_test.go (about) 1 package wallet 2 3 import ( 4 "sync" 5 "testing" 6 7 "gitlab.com/SiaPrime/SiaPrime/modules" 8 "gitlab.com/SiaPrime/SiaPrime/types" 9 ) 10 11 // addBlockNoPayout adds a block to the wallet tester that does not have any 12 // payouts. 13 func (wt *walletTester) addBlockNoPayout() error { 14 block, target, err := wt.miner.BlockForWork() 15 if err != nil { 16 return err 17 } 18 // Clear the miner payout so that the wallet is not getting additional 19 // outputs from these blocks. 20 for i := range block.MinerPayouts { 21 if i == len(block.MinerPayouts)-1 { 22 // DevSubsidy. 23 continue 24 } 25 block.MinerPayouts[i].UnlockHash = types.UnlockHash{} 26 } 27 28 // Solve and submit the block. 29 solvedBlock, _ := wt.miner.SolveBlock(block, target) 30 err = wt.cs.AcceptBlock(solvedBlock) 31 if err != nil { 32 return err 33 } 34 return nil 35 } 36 37 // TestViewAdded checks that 'ViewAdded' returns sane-seeming values when 38 // indicating which elements have been added automatically to a transaction 39 // set. 40 func TestViewAdded(t *testing.T) { 41 if testing.Short() { 42 t.SkipNow() 43 } 44 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 45 if err != nil { 46 t.Fatal(err) 47 } 48 defer wt.closeWt() 49 50 // Mine an extra block to get more outputs - the wallet is going to be 51 // loading two transactions at the same time. 52 _, err = wt.miner.AddBlock() 53 if err != nil { 54 t.Fatal(err) 55 } 56 57 // Create a transaction, add money to it, spend the money in a miner fee 58 // but do not sign the transaction. The format of this test mimics the way 59 // that the host-renter protocol behaves when building a file contract 60 // transaction. 61 b, err := wt.wallet.StartTransaction() 62 if err != nil { 63 t.Fatal(err) 64 } 65 txnFund := types.NewCurrency64(100e9) 66 err = b.FundSiacoins(txnFund) 67 if err != nil { 68 t.Fatal(err) 69 } 70 _ = b.AddMinerFee(txnFund) 71 _ = b.AddSiacoinOutput(types.SiacoinOutput{Value: txnFund}) 72 unfinishedTxn, unfinishedParents := b.View() 73 74 // Create a second builder that extends the first, unsigned transaction. Do 75 // not sign the transaction, but do give the extensions to the original 76 // builder. 77 b2, err := wt.wallet.RegisterTransaction(unfinishedTxn, unfinishedParents) 78 if err != nil { 79 t.Fatal(err) 80 } 81 err = b2.FundSiacoins(txnFund) 82 if err != nil { 83 t.Fatal(err) 84 } 85 unfinishedTxn2, unfinishedParents2 := b2.View() 86 newParentIndices, newInputIndices, _, _ := b2.ViewAdded() 87 88 // Add the new elements from b2 to b and sign the transaction, fetching the 89 // signature for b. 90 for _, parentIndex := range newParentIndices { 91 b.AddParents([]types.Transaction{unfinishedParents2[parentIndex]}) 92 } 93 for _, inputIndex := range newInputIndices { 94 b.AddSiacoinInput(unfinishedTxn2.SiacoinInputs[inputIndex]) 95 } 96 // Signing with WholeTransaction=true makes the transaction more brittle to 97 // construction mistakes, meaning that an error is more likely to turn up. 98 set1, err := b.Sign(true) 99 if err != nil { 100 t.Fatal(err) 101 } 102 if set1[len(set1)-1].ID() == unfinishedTxn.ID() { 103 t.Error("seems like there's memory sharing happening between txn calls") 104 } 105 // Set1 should be missing some signatures. 106 err = wt.tpool.AcceptTransactionSet(set1) 107 if err == nil { 108 t.Fatal(err) 109 } 110 unfinishedTxn3, _ := b.View() 111 // Only the new signatures are needed because the previous call to 'View' 112 // included everything else. 113 _, _, _, newTxnSignaturesIndices := b.ViewAdded() 114 115 // Add the new signatures to b2, and then sign b2's inputs. The resulting 116 // set from b2 should be valid. 117 for _, sigIndex := range newTxnSignaturesIndices { 118 b2.AddTransactionSignature(unfinishedTxn3.TransactionSignatures[sigIndex]) 119 } 120 set2, err := b2.Sign(true) 121 if err != nil { 122 t.Fatal(err) 123 } 124 err = wt.tpool.AcceptTransactionSet(set2) 125 if err != nil { 126 t.Fatal(err) 127 } 128 finishedTxn, _ := b2.View() 129 _, _, _, newTxnSignaturesIndices3 := b2.ViewAdded() 130 131 // Add the new signatures from b2 to the b1 transaction, which should 132 // complete the transaction and create a transaction set in 'b' that is 133 // identical to the transaction set that is in b2. 134 for _, sigIndex := range newTxnSignaturesIndices3 { 135 b.AddTransactionSignature(finishedTxn.TransactionSignatures[sigIndex]) 136 } 137 set3Txn, set3Parents := b.View() 138 err = wt.tpool.AcceptTransactionSet(append(set3Parents, set3Txn)) 139 if err != modules.ErrDuplicateTransactionSet { 140 t.Fatal(err) 141 } 142 } 143 144 // TestDoubleSignError checks that an error is returned if there is a problem 145 // when trying to call 'Sign' on a transaction twice. 146 func TestDoubleSignError(t *testing.T) { 147 if testing.Short() { 148 t.SkipNow() 149 } 150 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 151 if err != nil { 152 t.Fatal(err) 153 } 154 defer wt.closeWt() 155 156 // Create a transaction, add money to it, and then call sign twice. 157 b, err := wt.wallet.StartTransaction() 158 if err != nil { 159 t.Fatal(err) 160 } 161 txnFund := types.NewCurrency64(100e9) 162 err = b.FundSiacoins(txnFund) 163 if err != nil { 164 t.Fatal(err) 165 } 166 _ = b.AddMinerFee(txnFund) 167 txnSet, err := b.Sign(true) 168 if err != nil { 169 t.Fatal(err) 170 } 171 txnSet2, err := b.Sign(true) 172 if err != errBuilderAlreadySigned { 173 t.Error("the wrong error is being returned after a double call to sign") 174 } 175 if err != nil && txnSet2 != nil { 176 t.Error("errored call to sign did not return a nil txn set") 177 } 178 err = wt.tpool.AcceptTransactionSet(txnSet) 179 if err != nil { 180 t.Fatal(err) 181 } 182 } 183 184 // TestConcurrentBuilders checks that multiple transaction builders can safely 185 // be opened at the same time, and that they will make valid transactions when 186 // building concurrently. 187 func TestConcurrentBuilders(t *testing.T) { 188 if testing.Short() { 189 t.SkipNow() 190 } 191 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 192 if err != nil { 193 t.Fatal(err) 194 } 195 defer wt.closeWt() 196 197 // Mine a few more blocks so that the wallet has lots of outputs to pick 198 // from. 199 for i := 0; i < 5; i++ { 200 _, err := wt.miner.AddBlock() 201 if err != nil { 202 t.Fatal(err) 203 } 204 } 205 206 // Get a baseline balance for the wallet. 207 startingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance() 208 if err != nil { 209 t.Fatal(err) 210 } 211 startingOutgoing, startingIncoming, err := wt.wallet.UnconfirmedBalance() 212 if err != nil { 213 t.Fatal(err) 214 } 215 if !startingOutgoing.IsZero() { 216 t.Fatal(startingOutgoing) 217 } 218 if !startingIncoming.IsZero() { 219 t.Fatal(startingIncoming) 220 } 221 222 // Create two builders at the same time, then add money to each. 223 builder1, err := wt.wallet.StartTransaction() 224 if err != nil { 225 t.Fatal(err) 226 } 227 builder2, err := wt.wallet.StartTransaction() 228 if err != nil { 229 t.Fatal(err) 230 } 231 // Fund each builder with a siacoin output that is smaller than all of the 232 // outputs that the wallet should currently have. 233 funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision) 234 err = builder1.FundSiacoins(funding) 235 if err != nil { 236 t.Fatal(err) 237 } 238 err = builder2.FundSiacoins(funding) 239 if err != nil { 240 t.Fatal(err) 241 } 242 243 // Get a second reading on the wallet's balance. 244 fundedSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance() 245 if err != nil { 246 t.Fatal(err) 247 } 248 if !startingSCConfirmed.Equals(fundedSCConfirmed) { 249 t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed) 250 } 251 252 // Spend the transaction funds on miner fees and the void output. 253 builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 254 builder2.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 255 // Send the money to the void. 256 output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)} 257 builder1.AddSiacoinOutput(output) 258 builder2.AddSiacoinOutput(output) 259 260 // Sign the transactions and verify that both are valid. 261 tset1, err := builder1.Sign(true) 262 if err != nil { 263 t.Fatal(err) 264 } 265 tset2, err := builder2.Sign(true) 266 if err != nil { 267 t.Fatal(err) 268 } 269 err = wt.tpool.AcceptTransactionSet(tset1) 270 if err != nil { 271 t.Fatal(err) 272 } 273 err = wt.tpool.AcceptTransactionSet(tset2) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 // Mine a block to get the transaction sets into the blockchain. 279 _, err = wt.miner.AddBlock() 280 if err != nil { 281 t.Fatal(err) 282 } 283 } 284 285 // TestConcurrentBuildersSingleOutput probes the behavior when multiple 286 // builders are created at the same time, but there is only a single wallet 287 // output that they end up needing to share. 288 func TestConcurrentBuildersSingleOutput(t *testing.T) { 289 if testing.Short() { 290 t.SkipNow() 291 } 292 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 293 if err != nil { 294 t.Fatal(err) 295 } 296 defer wt.closeWt() 297 298 // Mine MaturityDelay blocks on the wallet using blocks that don't give 299 // miner payouts to the wallet, so that all outputs can be condensed into a 300 // single confirmed output. Currently the wallet will be getting a new 301 // output per block because it has mined some blocks that haven't had their 302 // outputs matured. 303 for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ { 304 err = wt.addBlockNoPayout() 305 if err != nil { 306 t.Fatal(err) 307 } 308 } 309 310 // Send all coins to a single confirmed output for the wallet. 311 unlockConditions, err := wt.wallet.NextAddress() 312 if err != nil { 313 t.Fatal(err) 314 } 315 scBal, _, _, err := wt.wallet.ConfirmedBalance() 316 if err != nil { 317 t.Fatal(err) 318 } 319 // Use a custom builder so that there is no transaction fee. 320 builder, err := wt.wallet.StartTransaction() 321 if err != nil { 322 t.Fatal(err) 323 } 324 err = builder.FundSiacoins(scBal) 325 if err != nil { 326 t.Fatal(err) 327 } 328 output := types.SiacoinOutput{ 329 Value: scBal, 330 UnlockHash: unlockConditions.UnlockHash(), 331 } 332 builder.AddSiacoinOutput(output) 333 tSet, err := builder.Sign(true) 334 if err != nil { 335 t.Fatal(err) 336 } 337 err = wt.tpool.AcceptTransactionSet(tSet) 338 if err != nil { 339 t.Fatal(err) 340 } 341 // Get the transaction into the blockchain without giving a miner payout to 342 // the wallet. 343 err = wt.addBlockNoPayout() 344 if err != nil { 345 t.Fatal(err) 346 } 347 348 // Get a baseline balance for the wallet. 349 startingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance() 350 if err != nil { 351 t.Fatal(err) 352 } 353 startingOutgoing, startingIncoming, err := wt.wallet.UnconfirmedBalance() 354 if err != nil { 355 t.Fatal(err) 356 } 357 if !startingOutgoing.IsZero() { 358 t.Fatal(startingOutgoing) 359 } 360 if !startingIncoming.IsZero() { 361 t.Fatal(startingIncoming) 362 } 363 364 // Create two builders at the same time, then add money to each. 365 builder1, err := wt.wallet.StartTransaction() 366 if err != nil { 367 t.Fatal(err) 368 } 369 builder2, err := wt.wallet.StartTransaction() 370 if err != nil { 371 t.Fatal(err) 372 } 373 // Fund each builder with a siacoin output. 374 funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision) 375 err = builder1.FundSiacoins(funding) 376 if err != nil { 377 t.Fatal(err) 378 } 379 // This add should fail, blocking the builder from completion. 380 err = builder2.FundSiacoins(funding) 381 if err != modules.ErrIncompleteTransactions { 382 t.Fatal(err) 383 } 384 385 // Get a second reading on the wallet's balance. 386 fundedSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance() 387 if err != nil { 388 t.Fatal(err) 389 } 390 if !startingSCConfirmed.Equals(fundedSCConfirmed) { 391 t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed) 392 } 393 394 // Spend the transaction funds on miner fees and the void output. 395 builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 396 // Send the money to the void. 397 output = types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)} 398 builder1.AddSiacoinOutput(output) 399 400 // Sign the transaction and submit it. 401 tset1, err := builder1.Sign(true) 402 if err != nil { 403 t.Fatal(err) 404 } 405 err = wt.tpool.AcceptTransactionSet(tset1) 406 if err != nil { 407 t.Fatal(err) 408 } 409 410 // Mine a block to get the transaction sets into the blockchain. 411 _, err = wt.miner.AddBlock() 412 if err != nil { 413 t.Fatal(err) 414 } 415 } 416 417 // TestParallelBuilders checks that multiple transaction builders can safely be 418 // opened at the same time, and that they will make valid transactions when 419 // building concurrently, using multiple gothreads to manage the builders. 420 func TestParallelBuilders(t *testing.T) { 421 if testing.Short() { 422 t.SkipNow() 423 } 424 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 425 if err != nil { 426 t.Fatal(err) 427 } 428 defer wt.closeWt() 429 430 // Mine a few more blocks so that the wallet has lots of outputs to pick 431 // from. 432 outputsDesired := 10 433 for i := 0; i < outputsDesired; i++ { 434 _, err := wt.miner.AddBlock() 435 if err != nil { 436 t.Fatal(err) 437 } 438 } 439 // Add MatruityDelay blocks with no payout to make tracking the balance 440 // easier. 441 for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ { 442 err = wt.addBlockNoPayout() 443 if err != nil { 444 t.Fatal(err) 445 } 446 } 447 448 // Get a baseline balance for the wallet. 449 startingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance() 450 if err != nil { 451 t.Fatal(err) 452 } 453 startingOutgoing, startingIncoming, err := wt.wallet.UnconfirmedBalance() 454 if err != nil { 455 t.Fatal(err) 456 } 457 if !startingOutgoing.IsZero() { 458 t.Fatal(startingOutgoing) 459 } 460 if !startingIncoming.IsZero() { 461 t.Fatal(startingIncoming) 462 } 463 464 // Create several builders in parallel. 465 var wg sync.WaitGroup 466 funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision) 467 for i := 0; i < outputsDesired; i++ { 468 wg.Add(1) 469 go func() { 470 // Create the builder and fund the transaction. 471 builder, err := wt.wallet.StartTransaction() 472 if err != nil { 473 t.Fatal(err) 474 } 475 err = builder.FundSiacoins(funding) 476 if err != nil { 477 t.Fatal(err) 478 } 479 480 // Spend the transaction funds on miner fees and the void output. 481 builder.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 482 output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)} 483 builder.AddSiacoinOutput(output) 484 // Sign the transactions and verify that both are valid. 485 tset, err := builder.Sign(true) 486 if err != nil { 487 t.Fatal(err) 488 } 489 err = wt.tpool.AcceptTransactionSet(tset) 490 if err != nil { 491 t.Fatal(err) 492 } 493 wg.Done() 494 }() 495 } 496 wg.Wait() 497 498 // Mine a block to get the transaction sets into the blockchain. 499 err = wt.addBlockNoPayout() 500 if err != nil { 501 t.Fatal(err) 502 } 503 504 // Check the final balance. 505 endingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance() 506 if err != nil { 507 t.Fatal(err) 508 } 509 expected := startingSCConfirmed.Sub(funding.Mul(types.NewCurrency64(uint64(outputsDesired)))) 510 if !expected.Equals(endingSCConfirmed) { 511 t.Fatal("did not get the expected ending balance", expected, endingSCConfirmed, startingSCConfirmed) 512 } 513 } 514 515 // TestUnconfirmedParents tests the functionality of the transaction builder's 516 // UnconfirmedParents method. 517 func TestUnconfirmedParents(t *testing.T) { 518 if testing.Short() { 519 t.SkipNow() 520 } 521 wt, err := createWalletTester(t.Name(), &modules.ProductionDependencies{}) 522 if err != nil { 523 t.Fatal(err) 524 } 525 defer wt.closeWt() 526 527 // Send all of the wallet's available balance to itself. 528 uc, err := wt.wallet.NextAddress() 529 if err != nil { 530 t.Fatal("Failed to get address", err) 531 } 532 siacoins, _, _, err := wt.wallet.ConfirmedBalance() 533 if err != nil { 534 t.Fatal(err) 535 } 536 tSet, err := wt.wallet.SendSiacoins(siacoins.Sub(types.SiacoinPrecision), uc.UnlockHash()) 537 if err != nil { 538 t.Fatal("Failed to send coins", err) 539 } 540 541 // Create a transaction. That transaction should use siacoin outputs from 542 // the unconfirmed transactions in tSet as inputs and is therefore a child 543 // of tSet. 544 b, err := wt.wallet.StartTransaction() 545 if err != nil { 546 t.Fatal(err) 547 } 548 txnFund := types.NewCurrency64(1e3) 549 err = b.FundSiacoins(txnFund) 550 if err != nil { 551 t.Fatal(err) 552 } 553 554 // UnconfirmedParents should return the transactions of the transaction set 555 // we used to send money to ourselves. 556 parents, err := b.UnconfirmedParents() 557 if err != nil { 558 t.Fatal(err) 559 } 560 if len(tSet) != len(parents) { 561 t.Fatal("parents should have same length as unconfirmed transaction set") 562 } 563 for i := 0; i < len(tSet); i++ { 564 if tSet[i].ID() != parents[i].ID() { 565 t.Error("returned parent doesn't match transaction of transaction set") 566 } 567 } 568 } 569 570 func TestFundSiacoinsForOutputs(t *testing.T) { 571 if testing.Short() { 572 t.SkipNow() 573 } 574 wt, err := createWalletTester(t.Name(), &modules.ProductionDependencies{}) 575 if err != nil { 576 t.Fatal(err) 577 } 578 defer wt.closeWt() 579 580 b, err := wt.wallet.StartTransaction() 581 if err != nil { 582 t.Fatal(err) 583 } 584 uc1, err := wt.wallet.NextAddress() 585 if err != nil { 586 t.Fatal(err) 587 } 588 uc2, err := wt.wallet.NextAddress() 589 if err != nil { 590 t.Fatal(err) 591 } 592 593 amount1 := types.NewCurrency64(1000) 594 amount2 := types.NewCurrency64(2000) 595 output1 := types.SiacoinOutput{ 596 Value: amount1, 597 UnlockHash: uc1.UnlockHash(), 598 } 599 output2 := types.SiacoinOutput{ 600 Value: amount2, 601 UnlockHash: uc2.UnlockHash(), 602 } 603 minerFee := types.NewCurrency64(750) 604 605 // Wallet starts off with large inputs from mining blocks, larger than our 606 // combined outputs and miner fees 607 err = b.FundSiacoinsForOutputs([]types.SiacoinOutput{output1, output2}, minerFee) 608 if err != nil { 609 t.Fatal(err) 610 } 611 unfinishedTxn, _ := b.View() 612 613 // Here we should have 3 outputs, the two specified plus a refund 614 if len(unfinishedTxn.SiacoinOutputs) != 3 { 615 t.Fatal("incorrect number of outputs generated") 616 } 617 if len(unfinishedTxn.MinerFees) != 1 { 618 t.Fatal("miner fees were not generated but should have been") 619 } 620 if unfinishedTxn.MinerFees[0].Cmp(minerFee) != 0 { 621 t.Fatal("miner fees were not generated but should have been") 622 } 623 624 // General construction seems ok, let's sign and submit it to the tpool 625 txSet, err := b.Sign(true) 626 if err != nil { 627 t.Fatal(err) 628 } 629 630 // If the tpool accepts it, everything looks good 631 err = wt.tpool.AcceptTransactionSet(txSet) 632 if err != nil { 633 t.Fatal(err) 634 } 635 }