github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/siatest/wallet/wallet_test.go (about)

     1  package wallet
     2  
     3  import (
     4  	"errors"
     5  	"path/filepath"
     6  	"testing"
     7  	"time"
     8  
     9  	"SiaPrime/build"
    10  	"SiaPrime/crypto"
    11  	"SiaPrime/modules"
    12  	"SiaPrime/node"
    13  	"SiaPrime/siatest"
    14  	"SiaPrime/types"
    15  )
    16  
    17  // TestTransactionReorg makes sure that a processedTransaction isn't returned
    18  // by the API after bein reverted.
    19  func TestTransactionReorg(t *testing.T) {
    20  	if testing.Short() {
    21  		t.SkipNow()
    22  	}
    23  
    24  	// Create testing directory.
    25  	testdir := walletTestDir(t.Name())
    26  
    27  	// Create two miners
    28  	miner1, err := siatest.NewNode(siatest.Miner(filepath.Join(testdir, "miner1")))
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  	defer func() {
    33  		if err := miner1.Close(); err != nil {
    34  			t.Fatal(err)
    35  		}
    36  	}()
    37  	// miner1 sends a txn to itself and mines it.
    38  	uc, err := miner1.WalletAddressGet()
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	wsp, err := miner1.WalletSiacoinsPost(types.SiacoinPrecision, uc.Address)
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	blocks := 1
    47  	for i := 0; i < blocks; i++ {
    48  		if err := miner1.MineBlock(); err != nil {
    49  			t.Fatal(err)
    50  		}
    51  	}
    52  	// wait until the transaction from before shows up as processed.
    53  	txn := wsp.TransactionIDs[len(wsp.TransactionIDs)-1]
    54  	err = build.Retry(100, 100*time.Millisecond, func() error {
    55  		cg, err := miner1.ConsensusGet()
    56  		if err != nil {
    57  			return err
    58  		}
    59  		wtg, err := miner1.WalletTransactionsGet(1, cg.Height)
    60  		if err != nil {
    61  			return err
    62  		}
    63  		for _, t := range wtg.ConfirmedTransactions {
    64  			if t.TransactionID == txn {
    65  				return nil
    66  			}
    67  		}
    68  		return errors.New("txn isn't processed yet")
    69  	})
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	miner2, err := siatest.NewNode(siatest.Miner(filepath.Join(testdir, "miner2")))
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  	defer func() {
    78  		if err := miner2.Close(); err != nil {
    79  			t.Fatal(err)
    80  		}
    81  	}()
    82  
    83  	// miner2 mines 2 blocks now to create a longer chain than miner1.
    84  	for i := 0; i < blocks+1; i++ {
    85  		if err := miner2.MineBlock(); err != nil {
    86  			t.Fatal(err)
    87  		}
    88  	}
    89  	// miner1 and miner2 connect. This should cause a reorg that reverts the
    90  	// transaction from before.
    91  	if err := miner1.GatewayConnectPost(miner2.GatewayAddress()); err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	err = build.Retry(100, 100*time.Millisecond, func() error {
    95  		cg, err := miner1.ConsensusGet()
    96  		if err != nil {
    97  			return err
    98  		}
    99  		wtg, err := miner1.WalletTransactionsGet(1, cg.Height)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		for _, t := range wtg.ConfirmedTransactions {
   104  			if t.TransactionID == txn {
   105  				return errors.New("txn is still processed")
   106  			}
   107  		}
   108  		return nil
   109  	})
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  }
   114  
   115  // TestSignTransaction is a integration test for signing transaction offline
   116  // using the API.
   117  func TestSignTransaction(t *testing.T) {
   118  	if testing.Short() {
   119  		t.SkipNow()
   120  	}
   121  
   122  	// Create a new server
   123  	testNode, err := siatest.NewNode(node.AllModules(siatest.TestDir(t.Name())))
   124  	if err != nil {
   125  		t.Fatal(err)
   126  	}
   127  	defer func() {
   128  		if err := testNode.Close(); err != nil {
   129  			t.Fatal(err)
   130  		}
   131  	}()
   132  
   133  	// get two outputs to spend
   134  	unspentResp, err := testNode.WalletUnspentGet()
   135  	if err != nil {
   136  		t.Fatal("failed to get spendable outputs:", err)
   137  	}
   138  	outputs := unspentResp.Outputs
   139  	wucg1, err := testNode.WalletUnlockConditionsGet(outputs[0].UnlockHash)
   140  	if err != nil {
   141  		t.Fatal("failed to get unlock conditions:", err)
   142  	}
   143  	wucg2, err := testNode.WalletUnlockConditionsGet(outputs[1].UnlockHash)
   144  	if err != nil {
   145  		t.Fatal("failed to get unlock conditions:", err)
   146  	}
   147  
   148  	// create a transaction that sends the outputs to the void, with no
   149  	// signatures
   150  	txn := types.Transaction{
   151  		SiacoinInputs: []types.SiacoinInput{
   152  			{
   153  				ParentID:         types.SiacoinOutputID(outputs[0].ID),
   154  				UnlockConditions: wucg1.UnlockConditions,
   155  			},
   156  			{
   157  				ParentID:         types.SiacoinOutputID(outputs[1].ID),
   158  				UnlockConditions: wucg2.UnlockConditions,
   159  			},
   160  		},
   161  		SiacoinOutputs: []types.SiacoinOutput{{
   162  			Value:      outputs[0].Value.Add(outputs[1].Value),
   163  			UnlockHash: types.UnlockHash{},
   164  		}},
   165  		TransactionSignatures: []types.TransactionSignature{
   166  			{ParentID: crypto.Hash(outputs[0].ID), CoveredFields: types.CoveredFields{WholeTransaction: true}},
   167  			{ParentID: crypto.Hash(outputs[1].ID), CoveredFields: types.CoveredFields{WholeTransaction: true}},
   168  		},
   169  	}
   170  
   171  	// sign the first input
   172  	signResp, err := testNode.WalletSignPost(txn, []crypto.Hash{txn.TransactionSignatures[0].ParentID})
   173  	if err != nil {
   174  		t.Fatal("failed to sign the transaction:", err)
   175  	}
   176  	txn = signResp.Transaction
   177  
   178  	// txn should now have one signature
   179  	if len(txn.TransactionSignatures[0].Signature) == 0 {
   180  		t.Fatal("transaction was not signed")
   181  	} else if len(txn.TransactionSignatures[1].Signature) != 0 {
   182  		t.Fatal("second input was also signed")
   183  	}
   184  
   185  	// sign the second input
   186  	signResp, err = testNode.WalletSignPost(txn, []crypto.Hash{txn.TransactionSignatures[1].ParentID})
   187  	if err != nil {
   188  		t.Fatal("failed to sign the transaction:", err)
   189  	}
   190  	txn = signResp.Transaction
   191  
   192  	// txn should now have both signatures
   193  	if len(txn.TransactionSignatures[0].Signature) == 0 || len(txn.TransactionSignatures[1].Signature) == 0 {
   194  		t.Fatal("transaction was not signed")
   195  	}
   196  
   197  	// the resulting transaction should be valid; submit it to the tpool and
   198  	// mine a block to confirm it
   199  	if err := testNode.TransactionPoolRawPost(txn, nil); err != nil {
   200  		t.Fatal("failed to add transaction to pool:", err)
   201  	}
   202  	if err := testNode.MineBlock(); err != nil {
   203  		t.Fatal("failed to mine block", err)
   204  	}
   205  
   206  	// the wallet should no longer list the resulting output as spendable
   207  	unspentResp, err = testNode.WalletUnspentGet()
   208  	if err != nil {
   209  		t.Fatal("failed to get spendable outputs")
   210  	}
   211  	for _, output := range unspentResp.Outputs {
   212  		if output.ID == types.OutputID(txn.SiacoinInputs[0].ParentID) {
   213  			t.Fatal("spent output still listed as spendable")
   214  		}
   215  	}
   216  }
   217  
   218  // TestWatchOnly tests the ability of the wallet to track addresses that it
   219  // does not own.
   220  func TestWatchOnly(t *testing.T) {
   221  	if testing.Short() {
   222  		t.SkipNow()
   223  	}
   224  
   225  	// Create a new server
   226  	testNode, err := siatest.NewNode(node.AllModules(siatest.TestDir(t.Name())))
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	defer func() {
   231  		if err := testNode.Close(); err != nil {
   232  			t.Fatal(err)
   233  		}
   234  	}()
   235  
   236  	// create an address manually and send coins to it
   237  	sk, pk := crypto.GenerateKeyPair()
   238  	uc := types.UnlockConditions{
   239  		PublicKeys:         []types.SiaPublicKey{types.Ed25519PublicKey(pk)},
   240  		SignaturesRequired: 1,
   241  	}
   242  	addr := uc.UnlockHash()
   243  
   244  	_, err = testNode.WalletSiacoinsPost(types.SiacoinPrecision.Mul64(77), addr)
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	testNode.MineBlock()
   249  
   250  	// the output should not show up in UnspentOutputs, because the address is
   251  	// not being tracked yet
   252  	unspentResp, err := testNode.WalletUnspentGet()
   253  	if err != nil {
   254  		t.Fatal("failed to get spendable outputs:", err)
   255  	} else if len(unspentResp.Outputs) == 0 {
   256  		t.Fatal("expected at least one unspent output")
   257  	}
   258  	for _, o := range unspentResp.Outputs {
   259  		if o.UnlockHash == addr {
   260  			t.Fatal("shouldn't see addr in UnspentOutputs yet")
   261  		}
   262  		if o.IsWatchOnly {
   263  			t.Error("no outputs should be marked watch-only yet")
   264  		}
   265  	}
   266  
   267  	// track the address
   268  	err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, false)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	// output should now show up
   274  	unspentResp, err = testNode.WalletUnspentGet()
   275  	if err != nil {
   276  		t.Fatal("failed to get spendable outputs:", err)
   277  	}
   278  	var output modules.UnspentOutput
   279  	for _, o := range unspentResp.Outputs {
   280  		if o.UnlockHash == addr {
   281  			output = o
   282  			break
   283  		}
   284  	}
   285  	if output.ID == (types.OutputID{}) {
   286  		t.Fatal("addr not present in UnspentOutputs after WatchAddresses")
   287  	}
   288  	if !output.IsWatchOnly {
   289  		t.Error("output should be marked watch-only")
   290  	}
   291  
   292  	// create a transaction that sends an output to the void
   293  	txn := types.Transaction{
   294  		SiacoinInputs: []types.SiacoinInput{{
   295  			ParentID:         types.SiacoinOutputID(output.ID),
   296  			UnlockConditions: uc,
   297  		}},
   298  		SiacoinOutputs: []types.SiacoinOutput{{
   299  			Value:      output.Value,
   300  			UnlockHash: types.UnlockHash{},
   301  		}},
   302  		TransactionSignatures: []types.TransactionSignature{{
   303  			ParentID:      crypto.Hash(output.ID),
   304  			CoveredFields: types.CoveredFields{WholeTransaction: true},
   305  		}},
   306  	}
   307  
   308  	// sign the transaction
   309  	cg, err := testNode.ConsensusGet()
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	sig := crypto.SignHash(txn.SigHash(0, cg.Height), sk)
   314  	txn.TransactionSignatures[0].Signature = sig[:]
   315  
   316  	// the resulting transaction should be valid; submit it to the tpool and
   317  	// mine a block to confirm it
   318  	err = testNode.TransactionPoolRawPost(txn, nil)
   319  	if err != nil {
   320  		t.Fatal(err)
   321  	}
   322  	testNode.MineBlock()
   323  
   324  	// the wallet should no longer list the resulting output as spendable
   325  	unspentResp, err = testNode.WalletUnspentGet()
   326  	if err != nil {
   327  		t.Fatal("failed to get spendable outputs:", err)
   328  	}
   329  	for _, o := range unspentResp.Outputs {
   330  		if o.UnlockHash == addr {
   331  			t.Fatal("spent output still listed as spendable")
   332  		}
   333  	}
   334  }
   335  
   336  // TestUnspentOutputs tests the UnspentOutputs method of the wallet.
   337  func TestUnspentOutputs(t *testing.T) {
   338  	if testing.Short() {
   339  		t.SkipNow()
   340  	}
   341  
   342  	// Create a new server
   343  	testNode, err := siatest.NewNode(node.AllModules(siatest.TestDir(t.Name())))
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  	defer func() {
   348  		if err := testNode.Close(); err != nil {
   349  			t.Fatal(err)
   350  		}
   351  	}()
   352  
   353  	// create a dummy address and send coins to it
   354  	addr := types.UnlockHash{1}
   355  
   356  	_, err = testNode.WalletSiacoinsPost(types.SiacoinPrecision.Mul64(77), addr)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	testNode.MineBlock()
   361  
   362  	// define a helper function to check whether addr appears in
   363  	// UnspentOutputs
   364  	addrIsPresent := func() bool {
   365  		wug, err := testNode.WalletUnspentGet()
   366  		if err != nil {
   367  			t.Fatal(err)
   368  		}
   369  		for _, o := range wug.Outputs {
   370  			if o.UnlockHash == addr {
   371  				return true
   372  			}
   373  		}
   374  		return false
   375  	}
   376  
   377  	// initially, the output should not show up in UnspentOutputs, because the
   378  	// address is not being tracked yet
   379  	if addrIsPresent() {
   380  		t.Fatal("shouldn't see addr in UnspentOutputs yet")
   381  	}
   382  
   383  	// add the address, but tell the wallet it hasn't been used yet. The
   384  	// wallet won't rescan, so it still won't see any outputs.
   385  	err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, true)
   386  	if err != nil {
   387  		t.Fatal(err)
   388  	}
   389  	if addrIsPresent() {
   390  		t.Fatal("shouldn't see addr in UnspentOutputs yet")
   391  	}
   392  
   393  	// remove the address, then add it again, this time telling the wallet
   394  	// that it has been used.
   395  	err = testNode.WalletWatchRemovePost([]types.UnlockHash{addr}, true)
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, false)
   400  	if err != nil {
   401  		t.Fatal(err)
   402  	}
   403  
   404  	// output should now show up
   405  	if !addrIsPresent() {
   406  		t.Fatal("addr not present in UnspentOutputs after AddWatchAddresses")
   407  	}
   408  
   409  	// remove the address, but tell the wallet that the address hasn't been
   410  	// used. The wallet won't rescan, so the output should still show up.
   411  	err = testNode.WalletWatchRemovePost([]types.UnlockHash{addr}, true)
   412  	if err != nil {
   413  		t.Fatal(err)
   414  	}
   415  	if !addrIsPresent() {
   416  		t.Fatal("addr should still be present in UnspentOutputs")
   417  	}
   418  
   419  	// add and remove the address again, this time triggering a rescan. The
   420  	// output should no longer appear.
   421  	err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, true)
   422  	if err != nil {
   423  		t.Fatal(err)
   424  	}
   425  	err = testNode.WalletWatchRemovePost([]types.UnlockHash{addr}, false)
   426  	if err != nil {
   427  		t.Fatal(err)
   428  	}
   429  	if addrIsPresent() {
   430  		t.Fatal("shouldn't see addr in UnspentOutputs")
   431  	}
   432  }