github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/wallet/transactionbuilder_test.go (about) 1 package wallet 2 3 import ( 4 "sync" 5 "testing" 6 7 "github.com/Synthesix/Sia/modules" 8 "github.com/Synthesix/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 := wt.wallet.StartTransaction() 58 txnFund := types.NewCurrency64(100e9) 59 err = b.FundSiacoins(txnFund) 60 if err != nil { 61 t.Fatal(err) 62 } 63 _ = b.AddMinerFee(txnFund) 64 _ = b.AddSiacoinOutput(types.SiacoinOutput{Value: txnFund}) 65 unfinishedTxn, unfinishedParents := b.View() 66 67 // Create a second builder that extends the first, unsigned transaction. Do 68 // not sign the transaction, but do give the extensions to the original 69 // builder. 70 b2 := wt.wallet.RegisterTransaction(unfinishedTxn, unfinishedParents) 71 err = b2.FundSiacoins(txnFund) 72 if err != nil { 73 t.Fatal(err) 74 } 75 unfinishedTxn2, unfinishedParents2 := b2.View() 76 newParentIndices, newInputIndices, _, _ := b2.ViewAdded() 77 78 // Add the new elements from b2 to b and sign the transaction, fetching the 79 // signature for b. 80 for _, parentIndex := range newParentIndices { 81 b.AddParents([]types.Transaction{unfinishedParents2[parentIndex]}) 82 } 83 for _, inputIndex := range newInputIndices { 84 b.AddSiacoinInput(unfinishedTxn2.SiacoinInputs[inputIndex]) 85 } 86 // Signing with WholeTransaction=true makes the transaction more brittle to 87 // construction mistakes, meaning that an error is more likely to turn up. 88 set1, err := b.Sign(true) 89 if err != nil { 90 t.Fatal(err) 91 } 92 if set1[len(set1)-1].ID() == unfinishedTxn.ID() { 93 t.Error("seems like there's memory sharing happening between txn calls") 94 } 95 // Set1 should be missing some signatures. 96 err = wt.tpool.AcceptTransactionSet(set1) 97 if err == nil { 98 t.Fatal(err) 99 } 100 unfinishedTxn3, _ := b.View() 101 // Only the new signatures are needed because the previous call to 'View' 102 // included everything else. 103 _, _, _, newTxnSignaturesIndices := b.ViewAdded() 104 105 // Add the new signatures to b2, and then sign b2's inputs. The resulting 106 // set from b2 should be valid. 107 for _, sigIndex := range newTxnSignaturesIndices { 108 b2.AddTransactionSignature(unfinishedTxn3.TransactionSignatures[sigIndex]) 109 } 110 set2, err := b2.Sign(true) 111 if err != nil { 112 t.Fatal(err) 113 } 114 err = wt.tpool.AcceptTransactionSet(set2) 115 if err != nil { 116 t.Fatal(err) 117 } 118 finishedTxn, _ := b2.View() 119 _, _, _, newTxnSignaturesIndices3 := b2.ViewAdded() 120 121 // Add the new signatures from b2 to the b1 transaction, which should 122 // complete the transaction and create a transaction set in 'b' that is 123 // identical to the transaction set that is in b2. 124 for _, sigIndex := range newTxnSignaturesIndices3 { 125 b.AddTransactionSignature(finishedTxn.TransactionSignatures[sigIndex]) 126 } 127 set3Txn, set3Parents := b.View() 128 err = wt.tpool.AcceptTransactionSet(append(set3Parents, set3Txn)) 129 if err != modules.ErrDuplicateTransactionSet { 130 t.Fatal(err) 131 } 132 } 133 134 // TestDoubleSignError checks that an error is returned if there is a problem 135 // when trying to call 'Sign' on a transaction twice. 136 func TestDoubleSignError(t *testing.T) { 137 if testing.Short() { 138 t.SkipNow() 139 } 140 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 141 if err != nil { 142 t.Fatal(err) 143 } 144 defer wt.closeWt() 145 146 // Create a transaction, add money to it, and then call sign twice. 147 b := wt.wallet.StartTransaction() 148 txnFund := types.NewCurrency64(100e9) 149 err = b.FundSiacoins(txnFund) 150 if err != nil { 151 t.Fatal(err) 152 } 153 _ = b.AddMinerFee(txnFund) 154 txnSet, err := b.Sign(true) 155 if err != nil { 156 t.Fatal(err) 157 } 158 txnSet2, err := b.Sign(true) 159 if err != errBuilderAlreadySigned { 160 t.Error("the wrong error is being returned after a double call to sign") 161 } 162 if err != nil && txnSet2 != nil { 163 t.Error("errored call to sign did not return a nil txn set") 164 } 165 err = wt.tpool.AcceptTransactionSet(txnSet) 166 if err != nil { 167 t.Fatal(err) 168 } 169 } 170 171 // TestConcurrentBuilders checks that multiple transaction builders can safely 172 // be opened at the same time, and that they will make valid transactions when 173 // building concurrently. 174 func TestConcurrentBuilders(t *testing.T) { 175 if testing.Short() { 176 t.SkipNow() 177 } 178 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 179 if err != nil { 180 t.Fatal(err) 181 } 182 defer wt.closeWt() 183 184 // Mine a few more blocks so that the wallet has lots of outputs to pick 185 // from. 186 for i := 0; i < 5; i++ { 187 _, err := wt.miner.AddBlock() 188 if err != nil { 189 t.Fatal(err) 190 } 191 } 192 193 // Get a baseline balance for the wallet. 194 startingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance() 195 startingOutgoing, startingIncoming := wt.wallet.UnconfirmedBalance() 196 if !startingOutgoing.IsZero() { 197 t.Fatal(startingOutgoing) 198 } 199 if !startingIncoming.IsZero() { 200 t.Fatal(startingIncoming) 201 } 202 203 // Create two builders at the same time, then add money to each. 204 builder1 := wt.wallet.StartTransaction() 205 builder2 := wt.wallet.StartTransaction() 206 // Fund each builder with a siacoin output that is smaller than all of the 207 // outputs that the wallet should currently have. 208 funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision) 209 err = builder1.FundSiacoins(funding) 210 if err != nil { 211 t.Fatal(err) 212 } 213 err = builder2.FundSiacoins(funding) 214 if err != nil { 215 t.Fatal(err) 216 } 217 218 // Get a second reading on the wallet's balance. 219 fundedSCConfirmed, _, _ := wt.wallet.ConfirmedBalance() 220 if !startingSCConfirmed.Equals(fundedSCConfirmed) { 221 t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed) 222 } 223 224 // Spend the transaction funds on miner fees and the void output. 225 builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 226 builder2.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 227 // Send the money to the void. 228 output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)} 229 builder1.AddSiacoinOutput(output) 230 builder2.AddSiacoinOutput(output) 231 232 // Sign the transactions and verify that both are valid. 233 tset1, err := builder1.Sign(true) 234 if err != nil { 235 t.Fatal(err) 236 } 237 tset2, err := builder2.Sign(true) 238 if err != nil { 239 t.Fatal(err) 240 } 241 err = wt.tpool.AcceptTransactionSet(tset1) 242 if err != nil { 243 t.Fatal(err) 244 } 245 err = wt.tpool.AcceptTransactionSet(tset2) 246 if err != nil { 247 t.Fatal(err) 248 } 249 250 // Mine a block to get the transaction sets into the blockchain. 251 _, err = wt.miner.AddBlock() 252 if err != nil { 253 t.Fatal(err) 254 } 255 } 256 257 // TestConcurrentBuildersSingleOutput probes the behavior when multiple 258 // builders are created at the same time, but there is only a single wallet 259 // output that they end up needing to share. 260 func TestConcurrentBuildersSingleOutput(t *testing.T) { 261 if testing.Short() { 262 t.SkipNow() 263 } 264 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 265 if err != nil { 266 t.Fatal(err) 267 } 268 defer wt.closeWt() 269 270 // Mine MaturityDelay blocks on the wallet using blocks that don't give 271 // miner payouts to the wallet, so that all outputs can be condensed into a 272 // single confirmed output. Currently the wallet will be getting a new 273 // output per block because it has mined some blocks that haven't had their 274 // outputs matured. 275 for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ { 276 err = wt.addBlockNoPayout() 277 if err != nil { 278 t.Fatal(err) 279 } 280 } 281 282 // Send all coins to a single confirmed output for the wallet. 283 unlockConditions, err := wt.wallet.NextAddress() 284 if err != nil { 285 t.Fatal(err) 286 } 287 scBal, _, _ := wt.wallet.ConfirmedBalance() 288 // Use a custom builder so that there is no transaction fee. 289 builder := wt.wallet.StartTransaction() 290 err = builder.FundSiacoins(scBal) 291 if err != nil { 292 t.Fatal(err) 293 } 294 output := types.SiacoinOutput{ 295 Value: scBal, 296 UnlockHash: unlockConditions.UnlockHash(), 297 } 298 builder.AddSiacoinOutput(output) 299 tSet, err := builder.Sign(true) 300 if err != nil { 301 t.Fatal(err) 302 } 303 err = wt.tpool.AcceptTransactionSet(tSet) 304 if err != nil { 305 t.Fatal(err) 306 } 307 // Get the transaction into the blockchain without giving a miner payout to 308 // the wallet. 309 err = wt.addBlockNoPayout() 310 if err != nil { 311 t.Fatal(err) 312 } 313 314 // Get a baseline balance for the wallet. 315 startingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance() 316 startingOutgoing, startingIncoming := wt.wallet.UnconfirmedBalance() 317 if !startingOutgoing.IsZero() { 318 t.Fatal(startingOutgoing) 319 } 320 if !startingIncoming.IsZero() { 321 t.Fatal(startingIncoming) 322 } 323 324 // Create two builders at the same time, then add money to each. 325 builder1 := wt.wallet.StartTransaction() 326 builder2 := wt.wallet.StartTransaction() 327 // Fund each builder with a siacoin output. 328 funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision) 329 err = builder1.FundSiacoins(funding) 330 if err != nil { 331 t.Fatal(err) 332 } 333 // This add should fail, blocking the builder from completion. 334 err = builder2.FundSiacoins(funding) 335 if err != modules.ErrIncompleteTransactions { 336 t.Fatal(err) 337 } 338 339 // Get a second reading on the wallet's balance. 340 fundedSCConfirmed, _, _ := wt.wallet.ConfirmedBalance() 341 if !startingSCConfirmed.Equals(fundedSCConfirmed) { 342 t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed) 343 } 344 345 // Spend the transaction funds on miner fees and the void output. 346 builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 347 // Send the money to the void. 348 output = types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)} 349 builder1.AddSiacoinOutput(output) 350 351 // Sign the transaction and submit it. 352 tset1, err := builder1.Sign(true) 353 if err != nil { 354 t.Fatal(err) 355 } 356 err = wt.tpool.AcceptTransactionSet(tset1) 357 if err != nil { 358 t.Fatal(err) 359 } 360 361 // Mine a block to get the transaction sets into the blockchain. 362 _, err = wt.miner.AddBlock() 363 if err != nil { 364 t.Fatal(err) 365 } 366 } 367 368 // TestParallelBuilders checks that multiple transaction builders can safely be 369 // opened at the same time, and that they will make valid transactions when 370 // building concurrently, using multiple gothreads to manage the builders. 371 func TestParallelBuilders(t *testing.T) { 372 if testing.Short() { 373 t.SkipNow() 374 } 375 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 376 if err != nil { 377 t.Fatal(err) 378 } 379 defer wt.closeWt() 380 381 // Mine a few more blocks so that the wallet has lots of outputs to pick 382 // from. 383 outputsDesired := 10 384 for i := 0; i < outputsDesired; i++ { 385 _, err := wt.miner.AddBlock() 386 if err != nil { 387 t.Fatal(err) 388 } 389 } 390 // Add MatruityDelay blocks with no payout to make tracking the balance 391 // easier. 392 for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ { 393 err = wt.addBlockNoPayout() 394 if err != nil { 395 t.Fatal(err) 396 } 397 } 398 399 // Get a baseline balance for the wallet. 400 startingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance() 401 startingOutgoing, startingIncoming := wt.wallet.UnconfirmedBalance() 402 if !startingOutgoing.IsZero() { 403 t.Fatal(startingOutgoing) 404 } 405 if !startingIncoming.IsZero() { 406 t.Fatal(startingIncoming) 407 } 408 409 // Create several builders in parallel. 410 var wg sync.WaitGroup 411 funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision) 412 for i := 0; i < outputsDesired; i++ { 413 wg.Add(1) 414 go func() { 415 // Create the builder and fund the transaction. 416 builder := wt.wallet.StartTransaction() 417 err := builder.FundSiacoins(funding) 418 if err != nil { 419 t.Fatal(err) 420 } 421 422 // Spend the transaction funds on miner fees and the void output. 423 builder.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision)) 424 output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)} 425 builder.AddSiacoinOutput(output) 426 // Sign the transactions and verify that both are valid. 427 tset, err := builder.Sign(true) 428 if err != nil { 429 t.Fatal(err) 430 } 431 err = wt.tpool.AcceptTransactionSet(tset) 432 if err != nil { 433 t.Fatal(err) 434 } 435 wg.Done() 436 }() 437 } 438 wg.Wait() 439 440 // Mine a block to get the transaction sets into the blockchain. 441 err = wt.addBlockNoPayout() 442 if err != nil { 443 t.Fatal(err) 444 } 445 446 // Check the final balance. 447 endingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance() 448 expected := startingSCConfirmed.Sub(funding.Mul(types.NewCurrency64(uint64(outputsDesired)))) 449 if !expected.Equals(endingSCConfirmed) { 450 t.Fatal("did not get the expected ending balance", expected, endingSCConfirmed, startingSCConfirmed) 451 } 452 }