gitlab.com/jokerrs1/Sia@v1.3.2/node/api/wallet_test.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/url" 8 "os" 9 "path/filepath" 10 "testing" 11 "time" 12 13 "github.com/NebulousLabs/Sia/build" 14 "github.com/NebulousLabs/Sia/crypto" 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/modules/consensus" 17 "github.com/NebulousLabs/Sia/modules/gateway" 18 "github.com/NebulousLabs/Sia/modules/miner" 19 "github.com/NebulousLabs/Sia/modules/transactionpool" 20 "github.com/NebulousLabs/Sia/modules/wallet" 21 "github.com/NebulousLabs/Sia/types" 22 "github.com/NebulousLabs/fastrand" 23 ) 24 25 // TestWalletGETEncrypted probes the GET call to /wallet when the 26 // wallet has never been encrypted. 27 func TestWalletGETEncrypted(t *testing.T) { 28 if testing.Short() { 29 t.SkipNow() 30 } 31 t.Parallel() 32 // Check a wallet that has never been encrypted. 33 testdir := build.TempDir("api", t.Name()) 34 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 35 if err != nil { 36 t.Fatal("Failed to create gateway:", err) 37 } 38 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 39 if err != nil { 40 t.Fatal("Failed to create consensus set:", err) 41 } 42 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 43 if err != nil { 44 t.Fatal("Failed to create tpool:", err) 45 } 46 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 47 if err != nil { 48 t.Fatal("Failed to create wallet:", err) 49 } 50 srv, err := NewServer("localhost:0", "Sia-Agent", "", cs, nil, g, nil, nil, nil, tp, w) 51 if err != nil { 52 t.Fatal(err) 53 } 54 55 // Assemble the serverTester and start listening for api requests. 56 st := &serverTester{ 57 cs: cs, 58 gateway: g, 59 tpool: tp, 60 wallet: w, 61 62 server: srv, 63 } 64 errChan := make(chan error) 65 go func() { 66 listenErr := srv.Serve() 67 errChan <- listenErr 68 }() 69 defer func() { 70 err := <-errChan 71 if err != nil { 72 t.Fatalf("API server quit: %v", err) 73 } 74 }() 75 defer st.server.panicClose() 76 77 var wg WalletGET 78 err = st.getAPI("/wallet", &wg) 79 if err != nil { 80 t.Fatal(err) 81 } 82 if wg.Encrypted { 83 t.Error("Wallet has never been encrypted") 84 } 85 if wg.Unlocked { 86 t.Error("Wallet has never been unlocked") 87 } 88 } 89 90 // TestWalletEncrypt tries to encrypt and unlock the wallet through the api 91 // using a provided encryption key. 92 func TestWalletEncrypt(t *testing.T) { 93 if testing.Short() { 94 t.SkipNow() 95 } 96 t.Parallel() 97 98 testdir := build.TempDir("api", t.Name()) 99 100 walletPassword := "testpass" 101 key := crypto.TwofishKey(crypto.HashObject(walletPassword)) 102 103 st, err := assembleServerTester(key, testdir) 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 // lock the wallet 109 err = st.stdPostAPI("/wallet/lock", nil) 110 if err != nil { 111 t.Fatal(err) 112 } 113 114 // Use the password to call /wallet/unlock. 115 unlockValues := url.Values{} 116 unlockValues.Set("encryptionpassword", walletPassword) 117 err = st.stdPostAPI("/wallet/unlock", unlockValues) 118 if err != nil { 119 t.Fatal(err) 120 } 121 // Check that the wallet actually unlocked. 122 if !st.wallet.Unlocked() { 123 t.Error("wallet is not unlocked") 124 } 125 126 // reload the server and verify unlocking still works 127 err = st.server.Close() 128 if err != nil { 129 t.Fatal(err) 130 } 131 132 st2, err := assembleServerTester(st.walletKey, st.dir) 133 if err != nil { 134 t.Fatal(err) 135 } 136 defer st2.server.panicClose() 137 138 // lock the wallet 139 err = st2.stdPostAPI("/wallet/lock", nil) 140 if err != nil { 141 t.Fatal(err) 142 } 143 144 // Use the password to call /wallet/unlock. 145 err = st2.stdPostAPI("/wallet/unlock", unlockValues) 146 if err != nil { 147 t.Fatal(err) 148 } 149 // Check that the wallet actually unlocked. 150 if !st2.wallet.Unlocked() { 151 t.Error("wallet is not unlocked") 152 } 153 } 154 155 // TestWalletBlankEncrypt tries to encrypt and unlock the wallet 156 // through the api using a blank encryption key - meaning that the wallet seed 157 // returned by the encryption call can be used as the encryption key. 158 func TestWalletBlankEncrypt(t *testing.T) { 159 if testing.Short() { 160 t.SkipNow() 161 } 162 t.Parallel() 163 // Create a server object without encrypting or unlocking the wallet. 164 testdir := build.TempDir("api", t.Name()) 165 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 166 if err != nil { 167 t.Fatal(err) 168 } 169 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 170 if err != nil { 171 t.Fatal(err) 172 } 173 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 174 if err != nil { 175 t.Fatal(err) 176 } 177 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 178 if err != nil { 179 t.Fatal(err) 180 } 181 srv, err := NewServer("localhost:0", "Sia-Agent", "", cs, nil, g, nil, nil, nil, tp, w) 182 if err != nil { 183 t.Fatal(err) 184 } 185 // Assemble the serverTester. 186 st := &serverTester{ 187 cs: cs, 188 gateway: g, 189 tpool: tp, 190 wallet: w, 191 server: srv, 192 } 193 go func() { 194 listenErr := srv.Serve() 195 if listenErr != nil { 196 panic(listenErr) 197 } 198 }() 199 defer st.server.panicClose() 200 201 // Make a call to /wallet/init and get the seed. Provide no encryption 202 // key so that the encryption key is the seed that gets returned. 203 var wip WalletInitPOST 204 err = st.postAPI("/wallet/init", url.Values{}, &wip) 205 if err != nil { 206 t.Fatal(err) 207 } 208 // Use the seed to call /wallet/unlock. 209 unlockValues := url.Values{} 210 unlockValues.Set("encryptionpassword", wip.PrimarySeed) 211 err = st.stdPostAPI("/wallet/unlock", unlockValues) 212 if err != nil { 213 t.Fatal(err) 214 } 215 // Check that the wallet actually unlocked. 216 if !w.Unlocked() { 217 t.Error("wallet is not unlocked") 218 } 219 } 220 221 // TestIntegrationWalletInitSeed tries to encrypt and unlock the wallet 222 // through the api using a supplied seed. 223 func TestIntegrationWalletInitSeed(t *testing.T) { 224 if testing.Short() { 225 t.SkipNow() 226 } 227 // Create a server object without encrypting or unlocking the wallet. 228 testdir := build.TempDir("api", "TestIntegrationWalletInitSeed") 229 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 230 if err != nil { 231 t.Fatal(err) 232 } 233 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 234 if err != nil { 235 t.Fatal(err) 236 } 237 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 238 if err != nil { 239 t.Fatal(err) 240 } 241 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 242 if err != nil { 243 t.Fatal(err) 244 } 245 srv, err := NewServer("localhost:0", "Sia-Agent", "", cs, nil, g, nil, nil, nil, tp, w) 246 if err != nil { 247 t.Fatal(err) 248 } 249 // Assemble the serverTester. 250 st := &serverTester{ 251 cs: cs, 252 gateway: g, 253 tpool: tp, 254 wallet: w, 255 server: srv, 256 } 257 go func() { 258 listenErr := srv.Serve() 259 if listenErr != nil { 260 panic(listenErr) 261 } 262 }() 263 defer st.server.panicClose() 264 265 // Make a call to /wallet/init/seed using an invalid seed 266 qs := url.Values{} 267 qs.Set("seed", "foo") 268 err = st.stdPostAPI("/wallet/init/seed", qs) 269 if err == nil { 270 t.Fatal("expected error, got nil") 271 } 272 273 // Make a call to /wallet/init/seed. Provide no encryption key so that the 274 // encryption key is the seed. 275 var seed modules.Seed 276 fastrand.Read(seed[:]) 277 seedStr, _ := modules.SeedToString(seed, "english") 278 qs.Set("seed", seedStr) 279 err = st.stdPostAPI("/wallet/init/seed", qs) 280 if err != nil { 281 t.Fatal(err) 282 } 283 284 // Try to re-init the wallet using a different encryption key 285 qs.Set("encryptionpassword", "foo") 286 err = st.stdPostAPI("/wallet/init/seed", qs) 287 if err == nil { 288 t.Fatal("expected error, got nil") 289 } 290 291 // Use the seed to call /wallet/unlock. 292 unlockValues := url.Values{} 293 unlockValues.Set("encryptionpassword", seedStr) 294 err = st.stdPostAPI("/wallet/unlock", unlockValues) 295 if err != nil { 296 t.Fatal(err) 297 } 298 // Check that the wallet actually unlocked. 299 if !w.Unlocked() { 300 t.Error("wallet is not unlocked") 301 } 302 } 303 304 // TestWalletGETSiacoins probes the GET call to /wallet when the 305 // siacoin balance is being manipulated. 306 func TestWalletGETSiacoins(t *testing.T) { 307 if testing.Short() { 308 t.SkipNow() 309 } 310 t.Parallel() 311 st, err := createServerTester(t.Name()) 312 if err != nil { 313 t.Fatal(err) 314 } 315 defer st.server.panicClose() 316 317 // Check the initial wallet is encrypted, unlocked, and has the siacoins 318 // that got mined. 319 var wg WalletGET 320 err = st.getAPI("/wallet", &wg) 321 if err != nil { 322 t.Fatal(err) 323 } 324 if !wg.Encrypted { 325 t.Error("Wallet has been encrypted") 326 } 327 if !wg.Unlocked { 328 t.Error("Wallet has been unlocked") 329 } 330 if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1)) != 0 { 331 t.Error("reported wallet balance does not reflect the single block that has been mined") 332 } 333 if wg.UnconfirmedOutgoingSiacoins.Cmp64(0) != 0 { 334 t.Error("there should not be unconfirmed outgoing siacoins") 335 } 336 if wg.UnconfirmedIncomingSiacoins.Cmp64(0) != 0 { 337 t.Error("there should not be unconfirmed incoming siacoins") 338 } 339 340 // Send coins to a wallet address through the api. 341 var wag WalletAddressGET 342 err = st.getAPI("/wallet/address", &wag) 343 if err != nil { 344 t.Fatal(err) 345 } 346 sendSiacoinsValues := url.Values{} 347 sendSiacoinsValues.Set("amount", "1234") 348 sendSiacoinsValues.Set("destination", wag.Address.String()) 349 err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues) 350 if err != nil { 351 t.Fatal(err) 352 } 353 354 // Check that the wallet is reporting unconfirmed siacoins. 355 err = st.getAPI("/wallet", &wg) 356 if err != nil { 357 t.Fatal(err) 358 } 359 if !wg.Encrypted { 360 t.Error("Wallet has been encrypted") 361 } 362 if !wg.Unlocked { 363 t.Error("Wallet has been unlocked") 364 } 365 if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1)) != 0 { 366 t.Error("reported wallet balance does not reflect the single block that has been mined") 367 } 368 if wg.UnconfirmedOutgoingSiacoins.Cmp64(0) <= 0 { 369 t.Error("there should be unconfirmed outgoing siacoins") 370 } 371 if wg.UnconfirmedIncomingSiacoins.Cmp64(0) <= 0 { 372 t.Error("there should be unconfirmed incoming siacoins") 373 } 374 if wg.UnconfirmedOutgoingSiacoins.Cmp(wg.UnconfirmedIncomingSiacoins) <= 0 { 375 t.Error("net movement of siacoins should be outgoing (miner fees)") 376 } 377 378 // Mine a block and see that the unconfirmed balances reduce back to 379 // nothing. 380 _, err = st.miner.AddBlock() 381 if err != nil { 382 t.Fatal(err) 383 } 384 err = st.getAPI("/wallet", &wg) 385 if err != nil { 386 t.Fatal(err) 387 } 388 if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1).Add(types.CalculateCoinbase(2))) >= 0 { 389 t.Error("reported wallet balance does not reflect mining two blocks and eating a miner fee") 390 } 391 if wg.UnconfirmedOutgoingSiacoins.Cmp64(0) != 0 { 392 t.Error("there should not be unconfirmed outgoing siacoins") 393 } 394 if wg.UnconfirmedIncomingSiacoins.Cmp64(0) != 0 { 395 t.Error("there should not be unconfirmed incoming siacoins") 396 } 397 } 398 399 // TestIntegrationWalletSweepSeedPOST probes the POST call to 400 // /wallet/sweep/seed. 401 func TestIntegrationWalletSweepSeedPOST(t *testing.T) { 402 if testing.Short() { 403 t.SkipNow() 404 } 405 st, err := createServerTester(t.Name()) 406 if err != nil { 407 t.Fatal(err) 408 } 409 defer st.server.panicClose() 410 411 // send coins to a new wallet, then sweep them back 412 key := crypto.GenerateTwofishKey() 413 w, err := wallet.New(st.cs, st.tpool, filepath.Join(st.dir, "wallet2")) 414 if err != nil { 415 t.Fatal(err) 416 } 417 _, err = w.Encrypt(key) 418 if err != nil { 419 t.Fatal(err) 420 } 421 err = w.Unlock(key) 422 if err != nil { 423 t.Fatal(err) 424 } 425 addr, _ := w.NextAddress() 426 st.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(100), addr.UnlockHash()) 427 _, err = st.miner.AddBlock() 428 if err != nil { 429 t.Fatal(err) 430 } 431 432 seed, _, _ := w.PrimarySeed() 433 seedStr, _ := modules.SeedToString(seed, "english") 434 435 // Sweep the coins we sent 436 var wsp WalletSweepPOST 437 qs := url.Values{} 438 qs.Set("seed", seedStr) 439 err = st.postAPI("/wallet/sweep/seed", qs, &wsp) 440 if err != nil { 441 t.Fatal(err) 442 } 443 // Should have swept more than 80 SC 444 if wsp.Coins.Cmp(types.SiacoinPrecision.Mul64(80)) <= 0 { 445 t.Fatalf("swept fewer coins (%v SC) than expected %v+", wsp.Coins.Div(types.SiacoinPrecision), 80) 446 } 447 448 // Add a block so that the sweep transaction is processed 449 _, err = st.miner.AddBlock() 450 if err != nil { 451 t.Fatal(err) 452 } 453 454 // Sweep again; should find no coins. An error will be returned because 455 // the found coins cannot cover the transaction fee. 456 err = st.postAPI("/wallet/sweep/seed", qs, &wsp) 457 if err == nil { 458 t.Fatal("expected error, got nil") 459 } 460 461 // Call /wallet/sweep/seed with an invalid seed 462 qs.Set("seed", "foo") 463 err = st.postAPI("/wallet/sweep/seed", qs, &wsp) 464 if err == nil { 465 t.Fatal("expected error, got nil") 466 } 467 } 468 469 // TestIntegrationWalletLoadSeedPOST probes the POST call to 470 // /wallet/seed. 471 func TestIntegrationWalletLoadSeedPOST(t *testing.T) { 472 if testing.Short() { 473 t.SkipNow() 474 } 475 476 // Create a wallet. 477 key := crypto.TwofishKey(crypto.HashObject("password")) 478 st, err := assembleServerTester(key, build.TempDir("api", t.Name())) 479 if err != nil { 480 t.Fatal(err) 481 } 482 defer st.panicClose() 483 // Mine blocks until the wallet has confirmed money. 484 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 485 _, err = st.miner.AddBlock() 486 if err != nil { 487 t.Fatal(err) 488 } 489 } 490 491 // Create a wallet to load coins from. 492 key2 := crypto.GenerateTwofishKey() 493 w2, err := wallet.New(st.cs, st.tpool, filepath.Join(st.dir, "wallet2")) 494 if err != nil { 495 t.Fatal(err) 496 } 497 _, err = w2.Encrypt(key2) 498 if err != nil { 499 t.Fatal(err) 500 } 501 err = w2.Unlock(key2) 502 if err != nil { 503 t.Fatal(err) 504 } 505 // Mine coins into the second wallet. 506 m, err := miner.New(st.cs, st.tpool, w2, filepath.Join(st.dir, "miner2")) 507 if err != nil { 508 t.Fatal(err) 509 } 510 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 511 _, err = m.AddBlock() 512 if err != nil { 513 t.Fatal(err) 514 } 515 } 516 517 // Record starting balances. 518 oldBal, _, _ := st.wallet.ConfirmedBalance() 519 w2bal, _, _ := w2.ConfirmedBalance() 520 if w2bal.IsZero() { 521 t.Fatal("second wallet's balance should not be zero") 522 } 523 524 // Load the second wallet's seed into the first wallet 525 seed, _, _ := w2.PrimarySeed() 526 seedStr, _ := modules.SeedToString(seed, "english") 527 qs := url.Values{} 528 qs.Set("seed", seedStr) 529 qs.Set("encryptionpassword", "password") 530 err = st.stdPostAPI("/wallet/seed", qs) 531 if err != nil { 532 t.Fatal(err) 533 } 534 // First wallet should now have balance of both wallets 535 bal, _, _ := st.wallet.ConfirmedBalance() 536 if exp := oldBal.Add(w2bal); !bal.Equals(exp) { 537 t.Fatalf("wallet did not load seed correctly: expected %v coins, got %v", exp, bal) 538 } 539 } 540 541 // TestWalletTransactionGETid queries the /wallet/transaction/:id 542 // api call. 543 func TestWalletTransactionGETid(t *testing.T) { 544 if testing.Short() { 545 t.SkipNow() 546 } 547 t.Parallel() 548 st, err := createServerTester(t.Name()) 549 if err != nil { 550 t.Fatal(err) 551 } 552 defer st.server.panicClose() 553 554 // Mining blocks should have created transactions for the wallet containing 555 // miner payouts. Get the list of transactions. 556 var wtg WalletTransactionsGET 557 err = st.getAPI("/wallet/transactions?startheight=0&endheight=10", &wtg) 558 if err != nil { 559 t.Fatal(err) 560 } 561 if len(wtg.ConfirmedTransactions) == 0 { 562 t.Error("expecting a few wallet transactions, corresponding to miner payouts.") 563 } 564 if len(wtg.UnconfirmedTransactions) != 0 { 565 t.Error("expecting 0 unconfirmed transactions") 566 } 567 // A call to /wallet/transactions without startheight and endheight parameters 568 // should return a descriptive error message. 569 err = st.getAPI("/wallet/transactions", &wtg) 570 if err == nil || err.Error() != "startheight and endheight must be provided to a /wallet/transactions call." { 571 t.Error("expecting /wallet/transactions call with empty parameters to error") 572 } 573 574 // Query the details of the first transaction using 575 // /wallet/transaction/:id 576 var wtgid WalletTransactionGETid 577 wtgidQuery := fmt.Sprintf("/wallet/transaction/%s", wtg.ConfirmedTransactions[0].TransactionID) 578 err = st.getAPI(wtgidQuery, &wtgid) 579 if err != nil { 580 t.Fatal(err) 581 } 582 if len(wtgid.Transaction.Inputs) != 0 { 583 t.Error("miner payout should appear as an output, not an input") 584 } 585 if len(wtgid.Transaction.Outputs) != 1 { 586 t.Fatal("a single miner payout output should have been created") 587 } 588 if wtgid.Transaction.Outputs[0].FundType != types.SpecifierMinerPayout { 589 t.Error("fund type should be a miner payout") 590 } 591 if wtgid.Transaction.Outputs[0].Value.IsZero() { 592 t.Error("output should have a nonzero value") 593 } 594 595 // Query the details of a transaction where siacoins were sent. 596 // 597 // NOTE: We call the SendSiacoins method directly to get convenient access 598 // to the txid. 599 sentValue := types.SiacoinPrecision.Mul64(3) 600 txns, err := st.wallet.SendSiacoins(sentValue, types.UnlockHash{}) 601 if err != nil { 602 t.Fatal(err) 603 } 604 _, err = st.miner.AddBlock() 605 if err != nil { 606 t.Fatal(err) 607 } 608 609 var wtgid2 WalletTransactionGETid 610 err = st.getAPI(fmt.Sprintf("/wallet/transaction/%s", txns[1].ID()), &wtgid2) 611 if err != nil { 612 t.Fatal(err) 613 } 614 txn := wtgid2.Transaction 615 if txn.TransactionID != txns[1].ID() { 616 t.Error("wrong transaction was fetched") 617 } else if len(txn.Inputs) != 1 || len(txn.Outputs) != 2 { 618 t.Error("expected 1 input and 2 outputs, got", len(txn.Inputs), len(txn.Outputs)) 619 } else if !txn.Outputs[0].Value.Equals(sentValue) { 620 t.Errorf("expected first output to equal %v, got %v", sentValue, txn.Outputs[0].Value) 621 } else if exp := txn.Inputs[0].Value.Sub(sentValue); !txn.Outputs[1].Value.Equals(exp) { 622 t.Errorf("expected first output to equal %v, got %v", exp, txn.Outputs[1].Value) 623 } 624 625 // Create a second wallet and send money to that wallet. 626 st2, err := blankServerTester(t.Name() + "w2") 627 if err != nil { 628 t.Fatal(err) 629 } 630 err = fullyConnectNodes([]*serverTester{st, st2}) 631 if err != nil { 632 t.Fatal(err) 633 } 634 635 // Send a transaction from the one wallet to the other. 636 var wag WalletAddressGET 637 err = st2.getAPI("/wallet/address", &wag) 638 if err != nil { 639 t.Fatal(err) 640 } 641 sendSiacoinsValues := url.Values{} 642 sendSiacoinsValues.Set("amount", sentValue.String()) 643 sendSiacoinsValues.Set("destination", wag.Address.String()) 644 err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues) 645 if err != nil { 646 t.Fatal(err) 647 } 648 649 // Check the unconfirmed transactions in the sending wallet to see the id of 650 // the output being spent. 651 err = st.getAPI("/wallet/transactions?startheight=0&endheight=10000", &wtg) 652 if err != nil { 653 t.Fatal(err) 654 } 655 if len(wtg.UnconfirmedTransactions) != 2 { 656 t.Fatal("expecting two unconfirmed transactions in sender wallet") 657 } 658 // Get the id of the non-change output sent to the receiving wallet. 659 expectedOutputID := wtg.UnconfirmedTransactions[1].Outputs[0].ID 660 661 // Check the unconfirmed transactions struct to make sure all fields are 662 // filled out correctly in the receiving wallet. 663 err = st2.getAPI("/wallet/transactions?startheight=0&endheight=10000", &wtg) 664 if err != nil { 665 t.Fatal(err) 666 } 667 // There should be at least one unconfirmed transaction: 668 err = retry(50, time.Millisecond*100, func() error { 669 if len(wtg.UnconfirmedTransactions) < 1 { 670 return errors.New("unconfirmed transaction not found") 671 } 672 return nil 673 }) 674 // The unconfirmed transaction should have inputs and outputs, and both of 675 // those should have value. 676 for _, txn := range wtg.UnconfirmedTransactions { 677 if len(txn.Inputs) < 1 { 678 t.Fatal("transaction should have an input") 679 } 680 if len(txn.Outputs) < 1 { 681 t.Fatal("transactions should have outputs") 682 } 683 for _, input := range txn.Inputs { 684 if input.Value.IsZero() { 685 t.Error("input should not have zero value") 686 } 687 } 688 for _, output := range txn.Outputs { 689 if output.Value.IsZero() { 690 t.Error("output should not have zero value") 691 } 692 } 693 if txn.Outputs[0].ID != expectedOutputID { 694 t.Error("transactions should have matching output ids for the same transaction") 695 } 696 } 697 698 // Restart st2. 699 err = st2.server.Close() 700 if err != nil { 701 t.Fatal(err) 702 } 703 st2, err = assembleServerTester(st2.walletKey, st2.dir) 704 if err != nil { 705 t.Fatal(err) 706 } 707 err = st2.getAPI("/wallet/transactions?startheight=0&endheight=10000", &wtg) 708 if err != nil { 709 t.Fatal(err) 710 } 711 712 // Reconnect st2 and st. 713 err = fullyConnectNodes([]*serverTester{st, st2}) 714 if err != nil { 715 t.Fatal(err) 716 } 717 718 // Mine a block on st to get the transactions into the blockchain. 719 _, err = st.miner.AddBlock() 720 if err != nil { 721 t.Fatal(err) 722 } 723 _, err = synchronizationCheck([]*serverTester{st, st2}) 724 if err != nil { 725 t.Fatal(err) 726 } 727 err = st2.getAPI("/wallet/transactions?startheight=0&endheight=10000", &wtg) 728 if err != nil { 729 t.Fatal(err) 730 } 731 // There should be at least one confirmed transaction: 732 if len(wtg.ConfirmedTransactions) < 1 { 733 t.Fatal("confirmed transaction not found") 734 } 735 for _, txn := range wtg.ConfirmedTransactions { 736 if len(txn.Inputs) < 1 { 737 t.Fatal("transaction should have an input") 738 } 739 if len(txn.Outputs) < 1 { 740 t.Fatal("transactions should have outputs") 741 } 742 for _, input := range txn.Inputs { 743 if input.Value.IsZero() { 744 t.Error("input should not have zero value") 745 } 746 } 747 for _, output := range txn.Outputs { 748 if output.Value.IsZero() { 749 t.Error("output should not have zero value") 750 } 751 } 752 } 753 754 // Reset the wallet and see that the confirmed transactions are still there. 755 err = st2.server.Close() 756 if err != nil { 757 t.Fatal(err) 758 } 759 st2, err = assembleServerTester(st2.walletKey, st2.dir) 760 if err != nil { 761 t.Fatal(err) 762 } 763 defer st2.server.Close() 764 err = st2.getAPI("/wallet/transactions?startheight=0&endheight=10000", &wtg) 765 if err != nil { 766 t.Fatal(err) 767 } 768 // There should be at least one confirmed transaction: 769 if len(wtg.ConfirmedTransactions) < 1 { 770 t.Fatal("unconfirmed transaction not found") 771 } 772 // Check whether the confirmed transactions remain. 773 for _, txn := range wtg.ConfirmedTransactions { 774 if len(txn.Inputs) < 1 { 775 t.Fatal("transaction should have an input") 776 } 777 if len(txn.Outputs) < 1 { 778 t.Fatal("transactions should have outputs") 779 } 780 for _, input := range txn.Inputs { 781 if input.Value.IsZero() { 782 t.Error("input should not have zero value") 783 } 784 } 785 for _, output := range txn.Outputs { 786 if output.Value.IsZero() { 787 t.Error("output should not have zero value") 788 } 789 } 790 } 791 } 792 793 // Tests that the /wallet/backup call checks for relative paths. 794 func TestWalletRelativePathErrorBackup(t *testing.T) { 795 if testing.Short() { 796 t.SkipNow() 797 } 798 t.Parallel() 799 st, err := createServerTester(t.Name()) 800 if err != nil { 801 t.Fatal(err) 802 } 803 defer st.server.panicClose() 804 805 // Announce the host. 806 if err := st.announceHost(); err != nil { 807 t.Fatal(err) 808 } 809 810 // Create tmp directory for uploads/downloads. 811 walletTestDir := build.TempDir("wallet_relative_path_backup") 812 err = os.MkdirAll(walletTestDir, 0700) 813 if err != nil { 814 t.Fatal(err) 815 } 816 817 // Wallet backup should error if its destination is a relative path 818 backupAbsoluteError := "error when calling /wallet/backup: destination must be an absolute path" 819 // This should error. 820 err = st.stdGetAPI("/wallet/backup?destination=test_wallet.backup") 821 if err == nil || err.Error() != backupAbsoluteError { 822 t.Fatal(err) 823 } 824 // This as well. 825 err = st.stdGetAPI("/wallet/backup?destination=../test_wallet.backup") 826 if err == nil || err.Error() != backupAbsoluteError { 827 t.Fatal(err) 828 } 829 // This should succeed. 830 err = st.stdGetAPI("/wallet/backup?destination=" + filepath.Join(walletTestDir, "test_wallet.backup")) 831 if err != nil { 832 t.Fatal(err) 833 } 834 // Make sure the backup was actually created. 835 _, errStat := os.Stat(filepath.Join(walletTestDir, "test_wallet.backup")) 836 if errStat != nil { 837 t.Error(errStat) 838 } 839 } 840 841 // Tests that the /wallet/033x call checks for relative paths. 842 func TestWalletRelativePathError033x(t *testing.T) { 843 if testing.Short() { 844 t.SkipNow() 845 } 846 t.Parallel() 847 st, err := createServerTester(t.Name()) 848 if err != nil { 849 t.Fatal(err) 850 } 851 defer st.server.panicClose() 852 853 // Announce the host. 854 if err := st.announceHost(); err != nil { 855 t.Fatal(err) 856 } 857 858 // Create tmp directory for uploads/downloads. 859 walletTestDir := build.TempDir("wallet_relative_path_033x") 860 err = os.MkdirAll(walletTestDir, 0700) 861 if err != nil { 862 t.Fatal(err) 863 } 864 865 // Wallet loading from 033x should error if its source is a relative path 866 load033xAbsoluteError := "error when calling /wallet/033x: source must be an absolute path" 867 868 // This should fail. 869 load033xValues := url.Values{} 870 load033xValues.Set("source", "test.dat") 871 err = st.stdPostAPI("/wallet/033x", load033xValues) 872 if err == nil || err.Error() != load033xAbsoluteError { 873 t.Fatal(err) 874 } 875 876 // As should this. 877 load033xValues = url.Values{} 878 load033xValues.Set("source", "../test.dat") 879 err = st.stdPostAPI("/wallet/033x", load033xValues) 880 if err == nil || err.Error() != load033xAbsoluteError { 881 t.Fatal(err) 882 } 883 884 // This should succeed (though the wallet method will still return an error) 885 load033xValues = url.Values{} 886 if err = createRandFile(filepath.Join(walletTestDir, "test.dat"), 0); err != nil { 887 t.Fatal(err) 888 } 889 load033xValues.Set("source", filepath.Join(walletTestDir, "test.dat")) 890 err = st.stdPostAPI("/wallet/033x", load033xValues) 891 if err == nil || err.Error() == load033xAbsoluteError { 892 t.Fatal(err) 893 } 894 } 895 896 // Tests that the /wallet/siagkey call checks for relative paths. 897 func TestWalletRelativePathErrorSiag(t *testing.T) { 898 if testing.Short() { 899 t.SkipNow() 900 } 901 t.Parallel() 902 st, err := createServerTester(t.Name()) 903 if err != nil { 904 t.Fatal(err) 905 } 906 defer st.server.panicClose() 907 908 // Announce the host. 909 if err := st.announceHost(); err != nil { 910 t.Fatal(err) 911 } 912 913 // Create tmp directory for uploads/downloads. 914 walletTestDir := build.TempDir("wallet_relative_path_sig") 915 err = os.MkdirAll(walletTestDir, 0700) 916 if err != nil { 917 t.Fatal(err) 918 } 919 920 // Wallet loading from siag should error if its source is a relative path 921 loadSiagAbsoluteError := "error when calling /wallet/siagkey: keyfiles contains a non-absolute path" 922 923 // This should fail. 924 loadSiagValues := url.Values{} 925 loadSiagValues.Set("keyfiles", "test.dat") 926 err = st.stdPostAPI("/wallet/siagkey", loadSiagValues) 927 if err == nil || err.Error() != loadSiagAbsoluteError { 928 t.Fatal(err) 929 } 930 931 // As should this. 932 loadSiagValues = url.Values{} 933 loadSiagValues.Set("keyfiles", "../test.dat") 934 err = st.stdPostAPI("/wallet/siagkey", loadSiagValues) 935 if err == nil || err.Error() != loadSiagAbsoluteError { 936 t.Fatal(err) 937 } 938 939 // This should fail. 940 loadSiagValues = url.Values{} 941 loadSiagValues.Set("keyfiles", "/test.dat,test.dat,../test.dat") 942 err = st.stdPostAPI("/wallet/siagkey", loadSiagValues) 943 if err == nil || err.Error() != loadSiagAbsoluteError { 944 t.Fatal(err) 945 } 946 947 // As should this. 948 loadSiagValues = url.Values{} 949 loadSiagValues.Set("keyfiles", "../test.dat,/test.dat") 950 err = st.stdPostAPI("/wallet/siagkey", loadSiagValues) 951 if err == nil || err.Error() != loadSiagAbsoluteError { 952 t.Fatal(err) 953 } 954 955 // This should succeed. 956 loadSiagValues = url.Values{} 957 if err = createRandFile(filepath.Join(walletTestDir, "test.dat"), 0); err != nil { 958 t.Fatal(err) 959 } 960 loadSiagValues.Set("keyfiles", filepath.Join(walletTestDir, "test.dat")) 961 err = st.stdPostAPI("/wallet/siagkey", loadSiagValues) 962 if err == nil || err.Error() == loadSiagAbsoluteError { 963 t.Fatal(err) 964 } 965 966 // As should this. 967 loadSiagValues = url.Values{} 968 if err = createRandFile(filepath.Join(walletTestDir, "test1.dat"), 0); err != nil { 969 t.Fatal(err) 970 } 971 loadSiagValues.Set("keyfiles", filepath.Join(walletTestDir, "test.dat")+","+filepath.Join(walletTestDir, "test1.dat")) 972 err = st.stdPostAPI("/wallet/siagkey", loadSiagValues) 973 if err == nil || err.Error() == loadSiagAbsoluteError { 974 t.Fatal(err) 975 } 976 } 977 978 func TestWalletReset(t *testing.T) { 979 if testing.Short() { 980 t.SkipNow() 981 } 982 t.Parallel() 983 984 testdir := build.TempDir("api", t.Name()) 985 986 walletPassword := "testpass" 987 key := crypto.TwofishKey(crypto.HashObject(walletPassword)) 988 989 st, err := assembleServerTester(key, testdir) 990 if err != nil { 991 t.Fatal(err) 992 } 993 994 // lock the wallet 995 err = st.stdPostAPI("/wallet/lock", nil) 996 if err != nil { 997 t.Fatal(err) 998 } 999 1000 // reencrypt the wallet 1001 newPassword := "testpass2" 1002 newKey := crypto.TwofishKey(crypto.HashObject(newPassword)) 1003 1004 initValues := url.Values{} 1005 initValues.Set("force", "true") 1006 initValues.Set("encryptionpassword", newPassword) 1007 err = st.stdPostAPI("/wallet/init", initValues) 1008 if err != nil { 1009 t.Fatal(err) 1010 } 1011 1012 // Use the password to call /wallet/unlock. 1013 unlockValues := url.Values{} 1014 unlockValues.Set("encryptionpassword", newPassword) 1015 err = st.stdPostAPI("/wallet/unlock", unlockValues) 1016 if err != nil { 1017 t.Fatal(err) 1018 } 1019 // Check that the wallet actually unlocked. 1020 if !st.wallet.Unlocked() { 1021 t.Error("wallet is not unlocked") 1022 } 1023 1024 // reload the server and verify unlocking still works 1025 err = st.server.Close() 1026 if err != nil { 1027 t.Fatal(err) 1028 } 1029 1030 st2, err := assembleServerTester(newKey, st.dir) 1031 if err != nil { 1032 t.Fatal(err) 1033 } 1034 defer st2.server.panicClose() 1035 1036 // lock the wallet 1037 err = st2.stdPostAPI("/wallet/lock", nil) 1038 if err != nil { 1039 t.Fatal(err) 1040 } 1041 1042 // Use the password to call /wallet/unlock. 1043 err = st2.stdPostAPI("/wallet/unlock", unlockValues) 1044 if err != nil { 1045 t.Fatal(err) 1046 } 1047 // Check that the wallet actually unlocked. 1048 if !st2.wallet.Unlocked() { 1049 t.Error("wallet is not unlocked") 1050 } 1051 } 1052 1053 func TestWalletSiafunds(t *testing.T) { 1054 if testing.Short() { 1055 t.SkipNow() 1056 } 1057 t.Parallel() 1058 1059 walletPassword := "testpass" 1060 key := crypto.TwofishKey(crypto.HashObject(walletPassword)) 1061 testdir := build.TempDir("api", t.Name()) 1062 st, err := assembleServerTester(key, testdir) 1063 if err != nil { 1064 t.Fatal(err) 1065 } 1066 defer st.server.panicClose() 1067 1068 // mine some money 1069 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 1070 _, err := st.miner.AddBlock() 1071 if err != nil { 1072 t.Fatal(err) 1073 } 1074 } 1075 1076 // record transactions 1077 var wtg WalletTransactionsGET 1078 err = st.getAPI("/wallet/transactions?startheight=0&endheight=100", &wtg) 1079 if err != nil { 1080 t.Fatal(err) 1081 } 1082 numTxns := len(wtg.ConfirmedTransactions) 1083 1084 // load siafunds into the wallet 1085 siagPath, _ := filepath.Abs("../../types/siag0of1of1.siakey") 1086 loadSiagValues := url.Values{} 1087 loadSiagValues.Set("keyfiles", siagPath) 1088 loadSiagValues.Set("encryptionpassword", walletPassword) 1089 err = st.stdPostAPI("/wallet/siagkey", loadSiagValues) 1090 if err != nil { 1091 t.Fatal(err) 1092 } 1093 1094 err = st.getAPI("/wallet/transactions?startheight=0&endheight=100", &wtg) 1095 if err != nil { 1096 t.Fatal(err) 1097 } 1098 if len(wtg.ConfirmedTransactions) != numTxns+1 { 1099 t.Errorf("expected %v transactions, got %v", numTxns+1, len(wtg.ConfirmedTransactions)) 1100 } 1101 1102 // check balance 1103 var wg WalletGET 1104 err = st.getAPI("/wallet", &wg) 1105 if err != nil { 1106 t.Fatal(err) 1107 } 1108 if wg.SiafundBalance.Cmp64(2000) != 0 { 1109 t.Fatalf("bad siafund balance: expected %v, got %v", 2000, wg.SiafundBalance) 1110 } 1111 1112 // spend the siafunds into the wallet seed 1113 var wag WalletAddressGET 1114 err = st.getAPI("/wallet/address", &wag) 1115 if err != nil { 1116 t.Fatal(err) 1117 } 1118 sendSiafundsValues := url.Values{} 1119 sendSiafundsValues.Set("amount", "2000") 1120 sendSiafundsValues.Set("destination", wag.Address.String()) 1121 err = st.stdPostAPI("/wallet/siafunds", sendSiafundsValues) 1122 if err != nil { 1123 t.Fatal(err) 1124 } 1125 1126 // Announce the host and form an allowance with it. This will result in a 1127 // siafund claim. 1128 err = st.announceHost() 1129 if err != nil { 1130 t.Fatal(err) 1131 } 1132 err = st.setHostStorage() 1133 if err != nil { 1134 t.Fatal(err) 1135 } 1136 err = st.acceptContracts() 1137 if err != nil { 1138 t.Fatal(err) 1139 } 1140 // mine a block so that the announcement makes it into the blockchain 1141 _, err = st.miner.AddBlock() 1142 if err != nil { 1143 t.Fatal(err) 1144 } 1145 1146 // form allowance 1147 allowanceValues := url.Values{} 1148 testFunds := "10000000000000000000000000000" // 10k SC 1149 testPeriod := "20" 1150 allowanceValues.Set("funds", testFunds) 1151 allowanceValues.Set("period", testPeriod) 1152 allowanceValues.Set("renewwindow", testRenewWindow) 1153 allowanceValues.Set("hosts", fmt.Sprint(recommendedHosts)) 1154 err = st.stdPostAPI("/renter", allowanceValues) 1155 if err != nil { 1156 t.Fatal(err) 1157 } 1158 1159 // Block until allowance has finished forming. 1160 err = build.Retry(50, time.Millisecond*250, func() error { 1161 var rc RenterContracts 1162 err = st.getAPI("/renter/contracts", &rc) 1163 if err != nil { 1164 return errors.New("couldn't get renter stats") 1165 } 1166 if len(rc.Contracts) != 1 { 1167 return errors.New("no contracts") 1168 } 1169 return nil 1170 }) 1171 if err != nil { 1172 t.Fatal("allowance setting failed") 1173 } 1174 1175 // mine a block so that the file contract makes it into the blockchain 1176 _, err = st.miner.AddBlock() 1177 if err != nil { 1178 t.Fatal(err) 1179 } 1180 // wallet should now have a claim balance 1181 err = st.getAPI("/wallet", &wg) 1182 if err != nil { 1183 t.Fatal(err) 1184 } 1185 if wg.SiacoinClaimBalance.IsZero() { 1186 t.Fatal("expected non-zero claim balance") 1187 } 1188 } 1189 1190 // TestWalletVerifyAddress tests that the /wallet/verify/address/:addr endpoint 1191 // validates wallet addresses correctly. 1192 func TestWalletVerifyAddress(t *testing.T) { 1193 if testing.Short() { 1194 t.SkipNow() 1195 } 1196 t.Parallel() 1197 1198 st, err := createServerTester(t.Name()) 1199 if err != nil { 1200 t.Fatal(err) 1201 } 1202 defer st.server.panicClose() 1203 1204 var res WalletVerifyAddressGET 1205 fakeaddr := "thisisaninvalidwalletaddress" 1206 if err = st.getAPI("/wallet/verify/address/"+fakeaddr, &res); err != nil { 1207 t.Fatal(err) 1208 } 1209 if res.Valid == true { 1210 t.Fatal("expected /wallet/verify to fail an invalid address") 1211 } 1212 1213 var wag WalletAddressGET 1214 err = st.getAPI("/wallet/address", &wag) 1215 if err != nil { 1216 t.Fatal(err) 1217 } 1218 if err = st.getAPI("/wallet/verify/address/"+wag.Address.String(), &res); err != nil { 1219 t.Fatal(err) 1220 } 1221 if res.Valid == false { 1222 t.Fatal("expected /wallet/verify to pass a valid address") 1223 } 1224 } 1225 1226 // TestWalletChangePassword verifies that the /wallet/changepassword endpoint 1227 // works correctly and changes a wallet password. 1228 func TestWalletChangePassword(t *testing.T) { 1229 if testing.Short() { 1230 t.SkipNow() 1231 } 1232 t.Parallel() 1233 1234 testdir := build.TempDir("api", t.Name()) 1235 1236 originalPassword := "testpass" 1237 newPassword := "newpass" 1238 originalKey := crypto.TwofishKey(crypto.HashObject(originalPassword)) 1239 newKey := crypto.TwofishKey(crypto.HashObject(newPassword)) 1240 1241 st, err := assembleServerTester(originalKey, testdir) 1242 if err != nil { 1243 t.Fatal(err) 1244 } 1245 1246 // lock the wallet 1247 err = st.stdPostAPI("/wallet/lock", nil) 1248 if err != nil { 1249 t.Fatal(err) 1250 } 1251 1252 // Use the password to call /wallet/unlock. 1253 unlockValues := url.Values{} 1254 unlockValues.Set("encryptionpassword", originalPassword) 1255 err = st.stdPostAPI("/wallet/unlock", unlockValues) 1256 if err != nil { 1257 t.Fatal(err) 1258 } 1259 // Check that the wallet actually unlocked. 1260 if !st.wallet.Unlocked() { 1261 t.Error("wallet is not unlocked") 1262 } 1263 1264 // change the wallet key 1265 changeKeyValues := url.Values{} 1266 changeKeyValues.Set("encryptionpassword", originalPassword) 1267 changeKeyValues.Set("newpassword", newPassword) 1268 err = st.stdPostAPI("/wallet/changepassword", changeKeyValues) 1269 if err != nil { 1270 t.Fatal(err) 1271 } 1272 // wallet should still be unlocked 1273 if !st.wallet.Unlocked() { 1274 t.Fatal("changepassword locked the wallet") 1275 } 1276 1277 // lock the wallet and verify unlocking works with the new password 1278 err = st.stdPostAPI("/wallet/lock", nil) 1279 if err != nil { 1280 t.Fatal(err) 1281 } 1282 unlockValues.Set("encryptionpassword", newPassword) 1283 err = st.stdPostAPI("/wallet/unlock", unlockValues) 1284 if err != nil { 1285 t.Fatal(err) 1286 } 1287 // Check that the wallet actually unlocked. 1288 if !st.wallet.Unlocked() { 1289 t.Error("wallet is not unlocked") 1290 } 1291 1292 // reload the server and verify unlocking still works 1293 err = st.server.Close() 1294 if err != nil { 1295 t.Fatal(err) 1296 } 1297 1298 st2, err := assembleServerTester(newKey, st.dir) 1299 if err != nil { 1300 t.Fatal(err) 1301 } 1302 defer st2.server.panicClose() 1303 1304 // lock the wallet 1305 err = st2.stdPostAPI("/wallet/lock", nil) 1306 if err != nil { 1307 t.Fatal(err) 1308 } 1309 1310 // Use the password to call /wallet/unlock. 1311 err = st2.stdPostAPI("/wallet/unlock", unlockValues) 1312 if err != nil { 1313 t.Fatal(err) 1314 } 1315 // Check that the wallet actually unlocked. 1316 if !st2.wallet.Unlocked() { 1317 t.Error("wallet is not unlocked") 1318 } 1319 } 1320 1321 // TestWalletSiacoins tests the /wallet/siacoins endpoint, including sending 1322 // to multiple addresses. 1323 func TestWalletSiacoins(t *testing.T) { 1324 if testing.Short() || !build.VLONG { 1325 t.SkipNow() 1326 } 1327 t.Parallel() 1328 1329 st, err := createServerTester(t.Name()) 1330 if err != nil { 1331 t.Fatal(err) 1332 } 1333 defer st.server.panicClose() 1334 st2, err := blankServerTester(t.Name() + "-wallet2") 1335 if err != nil { 1336 t.Fatal(err) 1337 } 1338 defer st2.server.Close() 1339 st3, err := blankServerTester(t.Name() + "-wallet3") 1340 if err != nil { 1341 t.Fatal(err) 1342 } 1343 defer st3.server.Close() 1344 st4, err := blankServerTester(t.Name() + "-wallet4") 1345 if err != nil { 1346 t.Fatal(err) 1347 } 1348 defer st4.server.Close() 1349 st5, err := blankServerTester(t.Name() + "-wallet5") 1350 if err != nil { 1351 t.Fatal(err) 1352 } 1353 defer st5.server.Close() 1354 st6, err := blankServerTester(t.Name() + "-wallet6") 1355 if err != nil { 1356 t.Fatal(err) 1357 } 1358 defer st6.server.Close() 1359 1360 // Mine two more blocks with 'st' to get extra outputs to spend. 1361 for i := 0; i < 2; i++ { 1362 _, err := st.miner.AddBlock() 1363 if err != nil { 1364 t.Fatal(err) 1365 } 1366 } 1367 1368 // Connect all the wallets together. 1369 wallets := []*serverTester{st, st2, st3, st4, st5, st6} 1370 err = fullyConnectNodes(wallets) 1371 if err != nil { 1372 t.Fatal(err) 1373 } 1374 1375 // Send 10KS in a single-send to st2. 1376 sendAmount := types.SiacoinPrecision.Mul64(10000) 1377 var wag WalletAddressGET 1378 err = st2.getAPI("/wallet/address", &wag) 1379 if err != nil { 1380 t.Fatal(err) 1381 } 1382 sendSiacoinsValues := url.Values{} 1383 outputsJSON, _ := json.Marshal([]types.SiacoinOutput{{ 1384 UnlockHash: wag.Address, 1385 Value: sendAmount, 1386 }}) 1387 sendSiacoinsValues.Set("outputs", string(outputsJSON)) 1388 if err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues); err != nil { 1389 t.Fatal(err) 1390 } 1391 1392 // Send 10KS to 3, 4, 5 in a single send. 1393 var outputs []types.SiacoinOutput 1394 for _, w := range wallets[2:5] { 1395 var wag WalletAddressGET 1396 err = w.getAPI("/wallet/address", &wag) 1397 if err != nil { 1398 t.Fatal(err) 1399 } 1400 outputs = append(outputs, types.SiacoinOutput{ 1401 UnlockHash: wag.Address, 1402 Value: sendAmount, 1403 }) 1404 } 1405 outputsJSON, _ = json.Marshal(outputs) 1406 sendSiacoinsValues = url.Values{} 1407 sendSiacoinsValues.Set("outputs", string(outputsJSON)) 1408 if err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues); err != nil { 1409 t.Fatal(err) 1410 } 1411 1412 // Send 10KS to 6 through a joined 250 sends. 1413 outputs = nil 1414 smallSend := sendAmount.Div64(250) 1415 for i := 0; i < 250; i++ { 1416 var wag WalletAddressGET 1417 err = st6.getAPI("/wallet/address", &wag) 1418 if err != nil { 1419 t.Fatal(err) 1420 } 1421 outputs = append(outputs, types.SiacoinOutput{ 1422 UnlockHash: wag.Address, 1423 Value: smallSend, 1424 }) 1425 } 1426 outputsJSON, _ = json.Marshal(outputs) 1427 sendSiacoinsValues = url.Values{} 1428 sendSiacoinsValues.Set("outputs", string(outputsJSON)) 1429 if err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues); err != nil { 1430 t.Fatal(err) 1431 } 1432 1433 // Mine a block to confirm the send. 1434 _, err = st.miner.AddBlock() 1435 if err != nil { 1436 t.Fatal(err) 1437 } 1438 // Wait for the block to propagate. 1439 _, err = synchronizationCheck(wallets) 1440 if err != nil { 1441 t.Fatal(err) 1442 } 1443 1444 // Check that the wallets all have 10KS. 1445 for i, w := range wallets[1:] { 1446 var wg WalletGET 1447 err = w.getAPI("/wallet", &wg) 1448 if err != nil { 1449 t.Fatal(err) 1450 } 1451 if !wg.ConfirmedSiacoinBalance.Equals(sendAmount) { 1452 t.Errorf("wallet %d should have %v coins, has %v", i+2, sendAmount, wg.ConfirmedSiacoinBalance) 1453 } 1454 } 1455 } 1456 1457 // TestWalletGETDust tests the consistency of dustthreshold field in /wallet 1458 func TestWalletGETDust(t *testing.T) { 1459 if testing.Short() { 1460 t.SkipNow() 1461 } 1462 t.Parallel() 1463 st, err := createServerTester(t.Name()) 1464 if err != nil { 1465 t.Fatal(err) 1466 } 1467 1468 var wg WalletGET 1469 err = st.getAPI("/wallet", &wg) 1470 if err != nil { 1471 t.Fatal(err) 1472 } 1473 1474 dt := st.wallet.DustThreshold() 1475 if !dt.Equals(wg.DustThreshold) { 1476 t.Fatal("dustThreshold mismatch") 1477 } 1478 } 1479 1480 // testWalletTransactionEndpoint is a subtest that queries the transaction endpoint of a node. 1481 func testWalletTransactionEndpoint(t *testing.T, st *serverTester, expectedConfirmedTxns int) { 1482 // Mining blocks should have created transactions for the wallet containing 1483 // miner payouts. Get the list of transactions. 1484 var wtg WalletTransactionsGET 1485 err := st.getAPI("/wallet/transactions?startheight=0&endheight=-1", &wtg) 1486 if err != nil { 1487 t.Fatal(err) 1488 } 1489 if len(wtg.ConfirmedTransactions) != expectedConfirmedTxns { 1490 t.Fatalf("expected %v txns but was %v", expectedConfirmedTxns, len(wtg.ConfirmedTransactions)) 1491 } 1492 1493 // Query the details of all transactions using 1494 // /wallet/transaction/:id 1495 for _, txn := range wtg.ConfirmedTransactions { 1496 var wtgid WalletTransactionGETid 1497 wtgidQuery := fmt.Sprintf("/wallet/transaction/%s", txn.TransactionID) 1498 err = st.getAPI(wtgidQuery, &wtgid) 1499 if err != nil { 1500 t.Fatal(err) 1501 } 1502 if wtgid.Transaction.TransactionID != txn.TransactionID { 1503 t.Fatalf("Expected txn with id %v but was %v", txn.TransactionID, wtgid.Transaction.TransactionID) 1504 } 1505 } 1506 } 1507 1508 // testWalletTransactionEndpoint is a subtest that queries the transactions endpoint of a node. 1509 func testWalletTransactionsEndpoint(t *testing.T, st *serverTester, expectedConfirmedTxns int) { 1510 var wtg WalletTransactionsGET 1511 err := st.getAPI("/wallet/transactions?startheight=0&endheight=-1", &wtg) 1512 if err != nil { 1513 t.Fatal(err) 1514 } 1515 if len(wtg.ConfirmedTransactions) != expectedConfirmedTxns { 1516 t.Fatalf("expected %v txns but was %v", expectedConfirmedTxns, len(wtg.ConfirmedTransactions)) 1517 } 1518 totalTxns := len(wtg.ConfirmedTransactions) 1519 1520 // Query the details of all transactions one block at a time using 1521 // /wallet/transactions 1522 queriedTxns := 0 1523 for i := types.BlockHeight(0); i <= st.cs.Height(); i++ { 1524 err := st.getAPI(fmt.Sprintf("/wallet/transactions?startheight=%v&endheight=%v", i, i), &wtg) 1525 if err != nil { 1526 t.Fatal(err) 1527 } 1528 queriedTxns += len(wtg.ConfirmedTransactions) 1529 } 1530 if queriedTxns != totalTxns { 1531 t.Errorf("Expected %v txns but was %v", totalTxns, queriedTxns) 1532 } 1533 1534 queriedTxns = 0 1535 batchSize := types.BlockHeight(5) 1536 for i := types.BlockHeight(0); i <= st.cs.Height(); i += (batchSize + 1) { 1537 err := st.getAPI(fmt.Sprintf("/wallet/transactions?startheight=%v&endheight=%v", i, i+batchSize), &wtg) 1538 if err != nil { 1539 t.Fatal(err) 1540 } 1541 queriedTxns += len(wtg.ConfirmedTransactions) 1542 } 1543 if queriedTxns != totalTxns { 1544 t.Errorf("Expected %v txns but was %v", totalTxns, queriedTxns) 1545 } 1546 } 1547 1548 // TestWalletManyTransactions creates a wallet and sends a large number of 1549 // coins to itself. Afterwards it will execute subtests to test the wallet's 1550 // scalability. 1551 func TestWalletManyTransactions(t *testing.T) { 1552 if testing.Short() || !build.VLONG { 1553 t.SkipNow() 1554 } 1555 1556 // Declare tests that should be executed 1557 subtests := []struct { 1558 name string 1559 f func(*testing.T, *serverTester, int) 1560 }{ 1561 {"TestWalletTransactionEndpoint", testWalletTransactionEndpoint}, 1562 {"TestWalletTransactionsEndpoint", testWalletTransactionsEndpoint}, 1563 } 1564 1565 // Create tester 1566 st, err := createServerTester(t.Name()) 1567 if err != nil { 1568 t.Fatal(err) 1569 } 1570 defer st.server.panicClose() 1571 1572 // Disable defrag for the wallet 1573 st.wallet.SetSettings(modules.WalletSettings{ 1574 NoDefrag: true, 1575 }) 1576 1577 // Mining blocks should have created transactions for the wallet containing 1578 // miner payouts. Get the list of transactions. 1579 var wtg WalletTransactionsGET 1580 err = st.getAPI("/wallet/transactions?startheight=0&endheight=-1", &wtg) 1581 if err != nil { 1582 t.Fatal(err) 1583 } 1584 if len(wtg.ConfirmedTransactions) == 0 { 1585 t.Fatal("expecting a few wallet transactions, corresponding to miner payouts.") 1586 } 1587 if len(wtg.UnconfirmedTransactions) != 0 { 1588 t.Fatal("expecting 0 unconfirmed transactions") 1589 } 1590 1591 // Remember the number of confirmed transactions 1592 numConfirmedTxns := len(wtg.ConfirmedTransactions) 1593 1594 // Get lots of addresses from the wallet 1595 numTxns := uint64(10000) 1596 ucs, err := st.wallet.NextAddresses(numTxns) 1597 if err != nil { 1598 t.Fatal(err) 1599 } 1600 1601 // Send SC to each address. 1602 minedBlocks := 0 1603 for i, uc := range ucs { 1604 st.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash()) 1605 if i%100 == 0 { 1606 if _, err := st.miner.AddBlock(); err != nil { 1607 t.Fatal(err) 1608 } 1609 minedBlocks++ 1610 } 1611 } 1612 if _, err := st.miner.AddBlock(); err != nil { 1613 t.Fatal(err) 1614 } 1615 minedBlocks++ 1616 1617 // After sending numTxns times there should be 2*numTxns confirmed 1618 // transactions plus one for each mined block. Every send creates a setup 1619 // transaction and the actual transaction. 1620 err = st.getAPI("/wallet/transactions?startheight=0&endheight=-1", &wtg) 1621 if err != nil { 1622 t.Fatal(err) 1623 } 1624 expectedConfirmedTxns := numConfirmedTxns + int(2*numTxns) + minedBlocks 1625 if len(wtg.ConfirmedTransactions) != expectedConfirmedTxns { 1626 t.Fatalf("expecting %v confirmed transactions but was %v", expectedConfirmedTxns, 1627 len(wtg.ConfirmedTransactions)) 1628 } 1629 if len(wtg.UnconfirmedTransactions) != 0 { 1630 t.Fatal("expecting 0 unconfirmed transactions") 1631 } 1632 1633 // Execute tests 1634 for _, subtest := range subtests { 1635 t.Run(subtest.name, func(t *testing.T) { 1636 subtest.f(t, st, expectedConfirmedTxns) 1637 }) 1638 } 1639 } 1640 1641 // TestWalletTransactionsGETAddr queries the /wallet/transactions/:addr api 1642 // call. 1643 func TestWalletTransactionsGetAddr(t *testing.T) { 1644 if testing.Short() { 1645 t.SkipNow() 1646 } 1647 t.Parallel() 1648 st, err := createServerTester(t.Name()) 1649 if err != nil { 1650 t.Fatal(err) 1651 } 1652 defer st.server.panicClose() 1653 1654 // Create a second wallet. 1655 st2, err := blankServerTester(t.Name() + "w2") 1656 if err != nil { 1657 t.Fatal(err) 1658 } 1659 defer st2.server.panicClose() 1660 1661 err = fullyConnectNodes([]*serverTester{st, st2}) 1662 if err != nil { 1663 t.Fatal(err) 1664 } 1665 1666 // Get address of recipient 1667 uc, err := st2.wallet.NextAddress() 1668 if err != nil { 1669 t.Fatal(err) 1670 } 1671 addr := uc.UnlockHash() 1672 1673 // Sent some money to the address 1674 sentValue := types.SiacoinPrecision.Mul64(3) 1675 _, err = st.wallet.SendSiacoins(sentValue, addr) 1676 if err != nil { 1677 t.Fatal(err) 1678 } 1679 1680 // Query the details of the first transaction using 1681 // /wallet/transactions/:addr 1682 var wtga WalletTransactionsGETaddr 1683 wtgaQuery := fmt.Sprintf("/wallet/transactions/%s", addr) 1684 err = st.getAPI(wtgaQuery, &wtga) 1685 if err != nil { 1686 t.Fatal(err) 1687 } 1688 if len(wtga.UnconfirmedTransactions) != 1 || len(wtga.ConfirmedTransactions) != 0 { 1689 t.Errorf("There should be exactly 1 unconfirmed and 0 confirmed related txns") 1690 } 1691 1692 // Mine a block to get the transaction confirmed 1693 _, err = st.miner.AddBlock() 1694 if err != nil { 1695 t.Fatal(err) 1696 } 1697 1698 // See if they moved to the confirmed transactions after mining a block 1699 err = st.getAPI(wtgaQuery, &wtga) 1700 if err != nil { 1701 t.Fatal(err) 1702 } 1703 if len(wtga.UnconfirmedTransactions) != 0 || len(wtga.ConfirmedTransactions) != 1 { 1704 t.Errorf("There should be exactly 0 unconfirmed and 1 confirmed related txns") 1705 } 1706 }