github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/wallet/transactionbuilder_test.go (about)

     1  package wallet
     2  
     3  import (
     4  	"sync"
     5  	"testing"
     6  
     7  	"github.com/Synthesix/Sia/modules"
     8  	"github.com/Synthesix/Sia/types"
     9  )
    10  
    11  // addBlockNoPayout adds a block to the wallet tester that does not have any
    12  // payouts.
    13  func (wt *walletTester) addBlockNoPayout() error {
    14  	block, target, err := wt.miner.BlockForWork()
    15  	if err != nil {
    16  		return err
    17  	}
    18  	// Clear the miner payout so that the wallet is not getting additional
    19  	// outputs from these blocks.
    20  	for i := range block.MinerPayouts {
    21  		block.MinerPayouts[i].UnlockHash = types.UnlockHash{}
    22  	}
    23  
    24  	// Solve and submit the block.
    25  	solvedBlock, _ := wt.miner.SolveBlock(block, target)
    26  	err = wt.cs.AcceptBlock(solvedBlock)
    27  	if err != nil {
    28  		return err
    29  	}
    30  	return nil
    31  }
    32  
    33  // TestViewAdded checks that 'ViewAdded' returns sane-seeming values when
    34  // indicating which elements have been added automatically to a transaction
    35  // set.
    36  func TestViewAdded(t *testing.T) {
    37  	if testing.Short() {
    38  		t.SkipNow()
    39  	}
    40  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  	defer wt.closeWt()
    45  
    46  	// Mine an extra block to get more outputs - the wallet is going to be
    47  	// loading two transactions at the same time.
    48  	_, err = wt.miner.AddBlock()
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  
    53  	// Create a transaction, add money to it, spend the money in a miner fee
    54  	// but do not sign the transaction. The format of this test mimics the way
    55  	// that the host-renter protocol behaves when building a file contract
    56  	// transaction.
    57  	b := wt.wallet.StartTransaction()
    58  	txnFund := types.NewCurrency64(100e9)
    59  	err = b.FundSiacoins(txnFund)
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  	_ = b.AddMinerFee(txnFund)
    64  	_ = b.AddSiacoinOutput(types.SiacoinOutput{Value: txnFund})
    65  	unfinishedTxn, unfinishedParents := b.View()
    66  
    67  	// Create a second builder that extends the first, unsigned transaction. Do
    68  	// not sign the transaction, but do give the extensions to the original
    69  	// builder.
    70  	b2 := wt.wallet.RegisterTransaction(unfinishedTxn, unfinishedParents)
    71  	err = b2.FundSiacoins(txnFund)
    72  	if err != nil {
    73  		t.Fatal(err)
    74  	}
    75  	unfinishedTxn2, unfinishedParents2 := b2.View()
    76  	newParentIndices, newInputIndices, _, _ := b2.ViewAdded()
    77  
    78  	// Add the new elements from b2 to b and sign the transaction, fetching the
    79  	// signature for b.
    80  	for _, parentIndex := range newParentIndices {
    81  		b.AddParents([]types.Transaction{unfinishedParents2[parentIndex]})
    82  	}
    83  	for _, inputIndex := range newInputIndices {
    84  		b.AddSiacoinInput(unfinishedTxn2.SiacoinInputs[inputIndex])
    85  	}
    86  	// Signing with WholeTransaction=true makes the transaction more brittle to
    87  	// construction mistakes, meaning that an error is more likely to turn up.
    88  	set1, err := b.Sign(true)
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  	if set1[len(set1)-1].ID() == unfinishedTxn.ID() {
    93  		t.Error("seems like there's memory sharing happening between txn calls")
    94  	}
    95  	// Set1 should be missing some signatures.
    96  	err = wt.tpool.AcceptTransactionSet(set1)
    97  	if err == nil {
    98  		t.Fatal(err)
    99  	}
   100  	unfinishedTxn3, _ := b.View()
   101  	// Only the new signatures are needed because the previous call to 'View'
   102  	// included everything else.
   103  	_, _, _, newTxnSignaturesIndices := b.ViewAdded()
   104  
   105  	// Add the new signatures to b2, and then sign b2's inputs. The resulting
   106  	// set from b2 should be valid.
   107  	for _, sigIndex := range newTxnSignaturesIndices {
   108  		b2.AddTransactionSignature(unfinishedTxn3.TransactionSignatures[sigIndex])
   109  	}
   110  	set2, err := b2.Sign(true)
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	err = wt.tpool.AcceptTransactionSet(set2)
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  	finishedTxn, _ := b2.View()
   119  	_, _, _, newTxnSignaturesIndices3 := b2.ViewAdded()
   120  
   121  	// Add the new signatures from b2 to the b1 transaction, which should
   122  	// complete the transaction and create a transaction set in 'b' that is
   123  	// identical to the transaction set that is in b2.
   124  	for _, sigIndex := range newTxnSignaturesIndices3 {
   125  		b.AddTransactionSignature(finishedTxn.TransactionSignatures[sigIndex])
   126  	}
   127  	set3Txn, set3Parents := b.View()
   128  	err = wt.tpool.AcceptTransactionSet(append(set3Parents, set3Txn))
   129  	if err != modules.ErrDuplicateTransactionSet {
   130  		t.Fatal(err)
   131  	}
   132  }
   133  
   134  // TestDoubleSignError checks that an error is returned if there is a problem
   135  // when trying to call 'Sign' on a transaction twice.
   136  func TestDoubleSignError(t *testing.T) {
   137  	if testing.Short() {
   138  		t.SkipNow()
   139  	}
   140  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	defer wt.closeWt()
   145  
   146  	// Create a transaction, add money to it, and then call sign twice.
   147  	b := wt.wallet.StartTransaction()
   148  	txnFund := types.NewCurrency64(100e9)
   149  	err = b.FundSiacoins(txnFund)
   150  	if err != nil {
   151  		t.Fatal(err)
   152  	}
   153  	_ = b.AddMinerFee(txnFund)
   154  	txnSet, err := b.Sign(true)
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	txnSet2, err := b.Sign(true)
   159  	if err != errBuilderAlreadySigned {
   160  		t.Error("the wrong error is being returned after a double call to sign")
   161  	}
   162  	if err != nil && txnSet2 != nil {
   163  		t.Error("errored call to sign did not return a nil txn set")
   164  	}
   165  	err = wt.tpool.AcceptTransactionSet(txnSet)
   166  	if err != nil {
   167  		t.Fatal(err)
   168  	}
   169  }
   170  
   171  // TestConcurrentBuilders checks that multiple transaction builders can safely
   172  // be opened at the same time, and that they will make valid transactions when
   173  // building concurrently.
   174  func TestConcurrentBuilders(t *testing.T) {
   175  	if testing.Short() {
   176  		t.SkipNow()
   177  	}
   178  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  	defer wt.closeWt()
   183  
   184  	// Mine a few more blocks so that the wallet has lots of outputs to pick
   185  	// from.
   186  	for i := 0; i < 5; i++ {
   187  		_, err := wt.miner.AddBlock()
   188  		if err != nil {
   189  			t.Fatal(err)
   190  		}
   191  	}
   192  
   193  	// Get a baseline balance for the wallet.
   194  	startingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance()
   195  	startingOutgoing, startingIncoming := wt.wallet.UnconfirmedBalance()
   196  	if !startingOutgoing.IsZero() {
   197  		t.Fatal(startingOutgoing)
   198  	}
   199  	if !startingIncoming.IsZero() {
   200  		t.Fatal(startingIncoming)
   201  	}
   202  
   203  	// Create two builders at the same time, then add money to each.
   204  	builder1 := wt.wallet.StartTransaction()
   205  	builder2 := wt.wallet.StartTransaction()
   206  	// Fund each builder with a siacoin output that is smaller than all of the
   207  	// outputs that the wallet should currently have.
   208  	funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision)
   209  	err = builder1.FundSiacoins(funding)
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  	err = builder2.FundSiacoins(funding)
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	// Get a second reading on the wallet's balance.
   219  	fundedSCConfirmed, _, _ := wt.wallet.ConfirmedBalance()
   220  	if !startingSCConfirmed.Equals(fundedSCConfirmed) {
   221  		t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed)
   222  	}
   223  
   224  	// Spend the transaction funds on miner fees and the void output.
   225  	builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   226  	builder2.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   227  	// Send the money to the void.
   228  	output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)}
   229  	builder1.AddSiacoinOutput(output)
   230  	builder2.AddSiacoinOutput(output)
   231  
   232  	// Sign the transactions and verify that both are valid.
   233  	tset1, err := builder1.Sign(true)
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	tset2, err := builder2.Sign(true)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	err = wt.tpool.AcceptTransactionSet(tset1)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  	err = wt.tpool.AcceptTransactionSet(tset2)
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  
   250  	// Mine a block to get the transaction sets into the blockchain.
   251  	_, err = wt.miner.AddBlock()
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  }
   256  
   257  // TestConcurrentBuildersSingleOutput probes the behavior when multiple
   258  // builders are created at the same time, but there is only a single wallet
   259  // output that they end up needing to share.
   260  func TestConcurrentBuildersSingleOutput(t *testing.T) {
   261  	if testing.Short() {
   262  		t.SkipNow()
   263  	}
   264  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	defer wt.closeWt()
   269  
   270  	// Mine MaturityDelay blocks on the wallet using blocks that don't give
   271  	// miner payouts to the wallet, so that all outputs can be condensed into a
   272  	// single confirmed output. Currently the wallet will be getting a new
   273  	// output per block because it has mined some blocks that haven't had their
   274  	// outputs matured.
   275  	for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ {
   276  		err = wt.addBlockNoPayout()
   277  		if err != nil {
   278  			t.Fatal(err)
   279  		}
   280  	}
   281  
   282  	// Send all coins to a single confirmed output for the wallet.
   283  	unlockConditions, err := wt.wallet.NextAddress()
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	scBal, _, _ := wt.wallet.ConfirmedBalance()
   288  	// Use a custom builder so that there is no transaction fee.
   289  	builder := wt.wallet.StartTransaction()
   290  	err = builder.FundSiacoins(scBal)
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	output := types.SiacoinOutput{
   295  		Value:      scBal,
   296  		UnlockHash: unlockConditions.UnlockHash(),
   297  	}
   298  	builder.AddSiacoinOutput(output)
   299  	tSet, err := builder.Sign(true)
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  	err = wt.tpool.AcceptTransactionSet(tSet)
   304  	if err != nil {
   305  		t.Fatal(err)
   306  	}
   307  	// Get the transaction into the blockchain without giving a miner payout to
   308  	// the wallet.
   309  	err = wt.addBlockNoPayout()
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  
   314  	// Get a baseline balance for the wallet.
   315  	startingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance()
   316  	startingOutgoing, startingIncoming := wt.wallet.UnconfirmedBalance()
   317  	if !startingOutgoing.IsZero() {
   318  		t.Fatal(startingOutgoing)
   319  	}
   320  	if !startingIncoming.IsZero() {
   321  		t.Fatal(startingIncoming)
   322  	}
   323  
   324  	// Create two builders at the same time, then add money to each.
   325  	builder1 := wt.wallet.StartTransaction()
   326  	builder2 := wt.wallet.StartTransaction()
   327  	// Fund each builder with a siacoin output.
   328  	funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision)
   329  	err = builder1.FundSiacoins(funding)
   330  	if err != nil {
   331  		t.Fatal(err)
   332  	}
   333  	// This add should fail, blocking the builder from completion.
   334  	err = builder2.FundSiacoins(funding)
   335  	if err != modules.ErrIncompleteTransactions {
   336  		t.Fatal(err)
   337  	}
   338  
   339  	// Get a second reading on the wallet's balance.
   340  	fundedSCConfirmed, _, _ := wt.wallet.ConfirmedBalance()
   341  	if !startingSCConfirmed.Equals(fundedSCConfirmed) {
   342  		t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed)
   343  	}
   344  
   345  	// Spend the transaction funds on miner fees and the void output.
   346  	builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   347  	// Send the money to the void.
   348  	output = types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)}
   349  	builder1.AddSiacoinOutput(output)
   350  
   351  	// Sign the transaction and submit it.
   352  	tset1, err := builder1.Sign(true)
   353  	if err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	err = wt.tpool.AcceptTransactionSet(tset1)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  
   361  	// Mine a block to get the transaction sets into the blockchain.
   362  	_, err = wt.miner.AddBlock()
   363  	if err != nil {
   364  		t.Fatal(err)
   365  	}
   366  }
   367  
   368  // TestParallelBuilders checks that multiple transaction builders can safely be
   369  // opened at the same time, and that they will make valid transactions when
   370  // building concurrently, using multiple gothreads to manage the builders.
   371  func TestParallelBuilders(t *testing.T) {
   372  	if testing.Short() {
   373  		t.SkipNow()
   374  	}
   375  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  	defer wt.closeWt()
   380  
   381  	// Mine a few more blocks so that the wallet has lots of outputs to pick
   382  	// from.
   383  	outputsDesired := 10
   384  	for i := 0; i < outputsDesired; i++ {
   385  		_, err := wt.miner.AddBlock()
   386  		if err != nil {
   387  			t.Fatal(err)
   388  		}
   389  	}
   390  	// Add MatruityDelay blocks with no payout to make tracking the balance
   391  	// easier.
   392  	for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ {
   393  		err = wt.addBlockNoPayout()
   394  		if err != nil {
   395  			t.Fatal(err)
   396  		}
   397  	}
   398  
   399  	// Get a baseline balance for the wallet.
   400  	startingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance()
   401  	startingOutgoing, startingIncoming := wt.wallet.UnconfirmedBalance()
   402  	if !startingOutgoing.IsZero() {
   403  		t.Fatal(startingOutgoing)
   404  	}
   405  	if !startingIncoming.IsZero() {
   406  		t.Fatal(startingIncoming)
   407  	}
   408  
   409  	// Create several builders in parallel.
   410  	var wg sync.WaitGroup
   411  	funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision)
   412  	for i := 0; i < outputsDesired; i++ {
   413  		wg.Add(1)
   414  		go func() {
   415  			// Create the builder and fund the transaction.
   416  			builder := wt.wallet.StartTransaction()
   417  			err := builder.FundSiacoins(funding)
   418  			if err != nil {
   419  				t.Fatal(err)
   420  			}
   421  
   422  			// Spend the transaction funds on miner fees and the void output.
   423  			builder.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   424  			output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)}
   425  			builder.AddSiacoinOutput(output)
   426  			// Sign the transactions and verify that both are valid.
   427  			tset, err := builder.Sign(true)
   428  			if err != nil {
   429  				t.Fatal(err)
   430  			}
   431  			err = wt.tpool.AcceptTransactionSet(tset)
   432  			if err != nil {
   433  				t.Fatal(err)
   434  			}
   435  			wg.Done()
   436  		}()
   437  	}
   438  	wg.Wait()
   439  
   440  	// Mine a block to get the transaction sets into the blockchain.
   441  	err = wt.addBlockNoPayout()
   442  	if err != nil {
   443  		t.Fatal(err)
   444  	}
   445  
   446  	// Check the final balance.
   447  	endingSCConfirmed, _, _ := wt.wallet.ConfirmedBalance()
   448  	expected := startingSCConfirmed.Sub(funding.Mul(types.NewCurrency64(uint64(outputsDesired))))
   449  	if !expected.Equals(endingSCConfirmed) {
   450  		t.Fatal("did not get the expected ending balance", expected, endingSCConfirmed, startingSCConfirmed)
   451  	}
   452  }