gitlab.com/jokerrs1/Sia@v1.3.2/node/api/wallet_test.go (about)

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