github.com/ZuluSpl0it/Sia@v1.3.7/node/api/wallet_test.go (about)

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