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