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