gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/wallet_test.go (about)

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