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