github.com/ZuluSpl0it/Sia@v1.3.7/modules/wallet/transactionbuilder_test.go (about)

     1  package wallet
     2  
     3  import (
     4  	"sync"
     5  	"testing"
     6  
     7  	"github.com/NebulousLabs/Sia/modules"
     8  	"github.com/NebulousLabs/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, err := wt.wallet.StartTransaction()
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  	txnFund := types.NewCurrency64(100e9)
    62  	err = b.FundSiacoins(txnFund)
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  	_ = b.AddMinerFee(txnFund)
    67  	_ = b.AddSiacoinOutput(types.SiacoinOutput{Value: txnFund})
    68  	unfinishedTxn, unfinishedParents := b.View()
    69  
    70  	// Create a second builder that extends the first, unsigned transaction. Do
    71  	// not sign the transaction, but do give the extensions to the original
    72  	// builder.
    73  	b2, err := wt.wallet.RegisterTransaction(unfinishedTxn, unfinishedParents)
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  	err = b2.FundSiacoins(txnFund)
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	unfinishedTxn2, unfinishedParents2 := b2.View()
    82  	newParentIndices, newInputIndices, _, _ := b2.ViewAdded()
    83  
    84  	// Add the new elements from b2 to b and sign the transaction, fetching the
    85  	// signature for b.
    86  	for _, parentIndex := range newParentIndices {
    87  		b.AddParents([]types.Transaction{unfinishedParents2[parentIndex]})
    88  	}
    89  	for _, inputIndex := range newInputIndices {
    90  		b.AddSiacoinInput(unfinishedTxn2.SiacoinInputs[inputIndex])
    91  	}
    92  	// Signing with WholeTransaction=true makes the transaction more brittle to
    93  	// construction mistakes, meaning that an error is more likely to turn up.
    94  	set1, err := b.Sign(true)
    95  	if err != nil {
    96  		t.Fatal(err)
    97  	}
    98  	if set1[len(set1)-1].ID() == unfinishedTxn.ID() {
    99  		t.Error("seems like there's memory sharing happening between txn calls")
   100  	}
   101  	// Set1 should be missing some signatures.
   102  	err = wt.tpool.AcceptTransactionSet(set1)
   103  	if err == nil {
   104  		t.Fatal(err)
   105  	}
   106  	unfinishedTxn3, _ := b.View()
   107  	// Only the new signatures are needed because the previous call to 'View'
   108  	// included everything else.
   109  	_, _, _, newTxnSignaturesIndices := b.ViewAdded()
   110  
   111  	// Add the new signatures to b2, and then sign b2's inputs. The resulting
   112  	// set from b2 should be valid.
   113  	for _, sigIndex := range newTxnSignaturesIndices {
   114  		b2.AddTransactionSignature(unfinishedTxn3.TransactionSignatures[sigIndex])
   115  	}
   116  	set2, err := b2.Sign(true)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	err = wt.tpool.AcceptTransactionSet(set2)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	finishedTxn, _ := b2.View()
   125  	_, _, _, newTxnSignaturesIndices3 := b2.ViewAdded()
   126  
   127  	// Add the new signatures from b2 to the b1 transaction, which should
   128  	// complete the transaction and create a transaction set in 'b' that is
   129  	// identical to the transaction set that is in b2.
   130  	for _, sigIndex := range newTxnSignaturesIndices3 {
   131  		b.AddTransactionSignature(finishedTxn.TransactionSignatures[sigIndex])
   132  	}
   133  	set3Txn, set3Parents := b.View()
   134  	err = wt.tpool.AcceptTransactionSet(append(set3Parents, set3Txn))
   135  	if err != modules.ErrDuplicateTransactionSet {
   136  		t.Fatal(err)
   137  	}
   138  }
   139  
   140  // TestDoubleSignError checks that an error is returned if there is a problem
   141  // when trying to call 'Sign' on a transaction twice.
   142  func TestDoubleSignError(t *testing.T) {
   143  	if testing.Short() {
   144  		t.SkipNow()
   145  	}
   146  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	defer wt.closeWt()
   151  
   152  	// Create a transaction, add money to it, and then call sign twice.
   153  	b, err := wt.wallet.StartTransaction()
   154  	if err != nil {
   155  		t.Fatal(err)
   156  	}
   157  	txnFund := types.NewCurrency64(100e9)
   158  	err = b.FundSiacoins(txnFund)
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  	_ = b.AddMinerFee(txnFund)
   163  	txnSet, err := b.Sign(true)
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	txnSet2, err := b.Sign(true)
   168  	if err != errBuilderAlreadySigned {
   169  		t.Error("the wrong error is being returned after a double call to sign")
   170  	}
   171  	if err != nil && txnSet2 != nil {
   172  		t.Error("errored call to sign did not return a nil txn set")
   173  	}
   174  	err = wt.tpool.AcceptTransactionSet(txnSet)
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  }
   179  
   180  // TestConcurrentBuilders checks that multiple transaction builders can safely
   181  // be opened at the same time, and that they will make valid transactions when
   182  // building concurrently.
   183  func TestConcurrentBuilders(t *testing.T) {
   184  	if testing.Short() {
   185  		t.SkipNow()
   186  	}
   187  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   188  	if err != nil {
   189  		t.Fatal(err)
   190  	}
   191  	defer wt.closeWt()
   192  
   193  	// Mine a few more blocks so that the wallet has lots of outputs to pick
   194  	// from.
   195  	for i := 0; i < 5; i++ {
   196  		_, err := wt.miner.AddBlock()
   197  		if err != nil {
   198  			t.Fatal(err)
   199  		}
   200  	}
   201  
   202  	// Get a baseline balance for the wallet.
   203  	startingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance()
   204  	if err != nil {
   205  		t.Fatal(err)
   206  	}
   207  	startingOutgoing, startingIncoming, err := wt.wallet.UnconfirmedBalance()
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	if !startingOutgoing.IsZero() {
   212  		t.Fatal(startingOutgoing)
   213  	}
   214  	if !startingIncoming.IsZero() {
   215  		t.Fatal(startingIncoming)
   216  	}
   217  
   218  	// Create two builders at the same time, then add money to each.
   219  	builder1, err := wt.wallet.StartTransaction()
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	builder2, err := wt.wallet.StartTransaction()
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	// Fund each builder with a siacoin output that is smaller than all of the
   228  	// outputs that the wallet should currently have.
   229  	funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision)
   230  	err = builder1.FundSiacoins(funding)
   231  	if err != nil {
   232  		t.Fatal(err)
   233  	}
   234  	err = builder2.FundSiacoins(funding)
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	// Get a second reading on the wallet's balance.
   240  	fundedSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance()
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  	if !startingSCConfirmed.Equals(fundedSCConfirmed) {
   245  		t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed)
   246  	}
   247  
   248  	// Spend the transaction funds on miner fees and the void output.
   249  	builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   250  	builder2.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   251  	// Send the money to the void.
   252  	output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)}
   253  	builder1.AddSiacoinOutput(output)
   254  	builder2.AddSiacoinOutput(output)
   255  
   256  	// Sign the transactions and verify that both are valid.
   257  	tset1, err := builder1.Sign(true)
   258  	if err != nil {
   259  		t.Fatal(err)
   260  	}
   261  	tset2, err := builder2.Sign(true)
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	err = wt.tpool.AcceptTransactionSet(tset1)
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	err = wt.tpool.AcceptTransactionSet(tset2)
   270  	if err != nil {
   271  		t.Fatal(err)
   272  	}
   273  
   274  	// Mine a block to get the transaction sets into the blockchain.
   275  	_, err = wt.miner.AddBlock()
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  }
   280  
   281  // TestConcurrentBuildersSingleOutput probes the behavior when multiple
   282  // builders are created at the same time, but there is only a single wallet
   283  // output that they end up needing to share.
   284  func TestConcurrentBuildersSingleOutput(t *testing.T) {
   285  	if testing.Short() {
   286  		t.SkipNow()
   287  	}
   288  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  	defer wt.closeWt()
   293  
   294  	// Mine MaturityDelay blocks on the wallet using blocks that don't give
   295  	// miner payouts to the wallet, so that all outputs can be condensed into a
   296  	// single confirmed output. Currently the wallet will be getting a new
   297  	// output per block because it has mined some blocks that haven't had their
   298  	// outputs matured.
   299  	for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ {
   300  		err = wt.addBlockNoPayout()
   301  		if err != nil {
   302  			t.Fatal(err)
   303  		}
   304  	}
   305  
   306  	// Send all coins to a single confirmed output for the wallet.
   307  	unlockConditions, err := wt.wallet.NextAddress()
   308  	if err != nil {
   309  		t.Fatal(err)
   310  	}
   311  	scBal, _, _, err := wt.wallet.ConfirmedBalance()
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  	// Use a custom builder so that there is no transaction fee.
   316  	builder, err := wt.wallet.StartTransaction()
   317  	if err != nil {
   318  		t.Fatal(err)
   319  	}
   320  	err = builder.FundSiacoins(scBal)
   321  	if err != nil {
   322  		t.Fatal(err)
   323  	}
   324  	output := types.SiacoinOutput{
   325  		Value:      scBal,
   326  		UnlockHash: unlockConditions.UnlockHash(),
   327  	}
   328  	builder.AddSiacoinOutput(output)
   329  	tSet, err := builder.Sign(true)
   330  	if err != nil {
   331  		t.Fatal(err)
   332  	}
   333  	err = wt.tpool.AcceptTransactionSet(tSet)
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	// Get the transaction into the blockchain without giving a miner payout to
   338  	// the wallet.
   339  	err = wt.addBlockNoPayout()
   340  	if err != nil {
   341  		t.Fatal(err)
   342  	}
   343  
   344  	// Get a baseline balance for the wallet.
   345  	startingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance()
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	startingOutgoing, startingIncoming, err := wt.wallet.UnconfirmedBalance()
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  	if !startingOutgoing.IsZero() {
   354  		t.Fatal(startingOutgoing)
   355  	}
   356  	if !startingIncoming.IsZero() {
   357  		t.Fatal(startingIncoming)
   358  	}
   359  
   360  	// Create two builders at the same time, then add money to each.
   361  	builder1, err := wt.wallet.StartTransaction()
   362  	if err != nil {
   363  		t.Fatal(err)
   364  	}
   365  	builder2, err := wt.wallet.StartTransaction()
   366  	if err != nil {
   367  		t.Fatal(err)
   368  	}
   369  	// Fund each builder with a siacoin output.
   370  	funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision)
   371  	err = builder1.FundSiacoins(funding)
   372  	if err != nil {
   373  		t.Fatal(err)
   374  	}
   375  	// This add should fail, blocking the builder from completion.
   376  	err = builder2.FundSiacoins(funding)
   377  	if err != modules.ErrIncompleteTransactions {
   378  		t.Fatal(err)
   379  	}
   380  
   381  	// Get a second reading on the wallet's balance.
   382  	fundedSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance()
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	if !startingSCConfirmed.Equals(fundedSCConfirmed) {
   387  		t.Fatal("confirmed siacoin balance changed when no blocks have been mined", startingSCConfirmed, fundedSCConfirmed)
   388  	}
   389  
   390  	// Spend the transaction funds on miner fees and the void output.
   391  	builder1.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   392  	// Send the money to the void.
   393  	output = types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)}
   394  	builder1.AddSiacoinOutput(output)
   395  
   396  	// Sign the transaction and submit it.
   397  	tset1, err := builder1.Sign(true)
   398  	if err != nil {
   399  		t.Fatal(err)
   400  	}
   401  	err = wt.tpool.AcceptTransactionSet(tset1)
   402  	if err != nil {
   403  		t.Fatal(err)
   404  	}
   405  
   406  	// Mine a block to get the transaction sets into the blockchain.
   407  	_, err = wt.miner.AddBlock()
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  }
   412  
   413  // TestParallelBuilders checks that multiple transaction builders can safely be
   414  // opened at the same time, and that they will make valid transactions when
   415  // building concurrently, using multiple gothreads to manage the builders.
   416  func TestParallelBuilders(t *testing.T) {
   417  	if testing.Short() {
   418  		t.SkipNow()
   419  	}
   420  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   421  	if err != nil {
   422  		t.Fatal(err)
   423  	}
   424  	defer wt.closeWt()
   425  
   426  	// Mine a few more blocks so that the wallet has lots of outputs to pick
   427  	// from.
   428  	outputsDesired := 10
   429  	for i := 0; i < outputsDesired; i++ {
   430  		_, err := wt.miner.AddBlock()
   431  		if err != nil {
   432  			t.Fatal(err)
   433  		}
   434  	}
   435  	// Add MatruityDelay blocks with no payout to make tracking the balance
   436  	// easier.
   437  	for i := types.BlockHeight(0); i < types.MaturityDelay+1; i++ {
   438  		err = wt.addBlockNoPayout()
   439  		if err != nil {
   440  			t.Fatal(err)
   441  		}
   442  	}
   443  
   444  	// Get a baseline balance for the wallet.
   445  	startingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance()
   446  	if err != nil {
   447  		t.Fatal(err)
   448  	}
   449  	startingOutgoing, startingIncoming, err := wt.wallet.UnconfirmedBalance()
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	if !startingOutgoing.IsZero() {
   454  		t.Fatal(startingOutgoing)
   455  	}
   456  	if !startingIncoming.IsZero() {
   457  		t.Fatal(startingIncoming)
   458  	}
   459  
   460  	// Create several builders in parallel.
   461  	var wg sync.WaitGroup
   462  	funding := types.NewCurrency64(10e3).Mul(types.SiacoinPrecision)
   463  	for i := 0; i < outputsDesired; i++ {
   464  		wg.Add(1)
   465  		go func() {
   466  			// Create the builder and fund the transaction.
   467  			builder, err := wt.wallet.StartTransaction()
   468  			if err != nil {
   469  				t.Fatal(err)
   470  			}
   471  			err = builder.FundSiacoins(funding)
   472  			if err != nil {
   473  				t.Fatal(err)
   474  			}
   475  
   476  			// Spend the transaction funds on miner fees and the void output.
   477  			builder.AddMinerFee(types.NewCurrency64(25).Mul(types.SiacoinPrecision))
   478  			output := types.SiacoinOutput{Value: types.NewCurrency64(9975).Mul(types.SiacoinPrecision)}
   479  			builder.AddSiacoinOutput(output)
   480  			// Sign the transactions and verify that both are valid.
   481  			tset, err := builder.Sign(true)
   482  			if err != nil {
   483  				t.Fatal(err)
   484  			}
   485  			err = wt.tpool.AcceptTransactionSet(tset)
   486  			if err != nil {
   487  				t.Fatal(err)
   488  			}
   489  			wg.Done()
   490  		}()
   491  	}
   492  	wg.Wait()
   493  
   494  	// Mine a block to get the transaction sets into the blockchain.
   495  	err = wt.addBlockNoPayout()
   496  	if err != nil {
   497  		t.Fatal(err)
   498  	}
   499  
   500  	// Check the final balance.
   501  	endingSCConfirmed, _, _, err := wt.wallet.ConfirmedBalance()
   502  	if err != nil {
   503  		t.Fatal(err)
   504  	}
   505  	expected := startingSCConfirmed.Sub(funding.Mul(types.NewCurrency64(uint64(outputsDesired))))
   506  	if !expected.Equals(endingSCConfirmed) {
   507  		t.Fatal("did not get the expected ending balance", expected, endingSCConfirmed, startingSCConfirmed)
   508  	}
   509  }
   510  
   511  // TestUnconfirmedParents tests the functionality of the transaction builder's
   512  // UnconfirmedParents method.
   513  func TestUnconfirmedParents(t *testing.T) {
   514  	if testing.Short() {
   515  		t.SkipNow()
   516  	}
   517  	wt, err := createWalletTester(t.Name(), &modules.ProductionDependencies{})
   518  	if err != nil {
   519  		t.Fatal(err)
   520  	}
   521  	defer wt.closeWt()
   522  
   523  	// Send all of the wallet's available balance to itself.
   524  	uc, err := wt.wallet.NextAddress()
   525  	if err != nil {
   526  		t.Fatal("Failed to get address", err)
   527  	}
   528  	siacoins, _, _, err := wt.wallet.ConfirmedBalance()
   529  	if err != nil {
   530  		t.Fatal(err)
   531  	}
   532  	tSet, err := wt.wallet.SendSiacoins(siacoins.Sub(types.SiacoinPrecision), uc.UnlockHash())
   533  	if err != nil {
   534  		t.Fatal("Failed to send coins", err)
   535  	}
   536  
   537  	// Create a transaction. That transaction should use siacoin outputs from
   538  	// the unconfirmed transactions in tSet as inputs and is therefore a child
   539  	// of tSet.
   540  	b, err := wt.wallet.StartTransaction()
   541  	if err != nil {
   542  		t.Fatal(err)
   543  	}
   544  	txnFund := types.NewCurrency64(1e3)
   545  	err = b.FundSiacoins(txnFund)
   546  	if err != nil {
   547  		t.Fatal(err)
   548  	}
   549  
   550  	// UnconfirmedParents should return the transactions of the transaction set
   551  	// we used to send money to ourselves.
   552  	parents, err := b.UnconfirmedParents()
   553  	if err != nil {
   554  		t.Fatal(err)
   555  	}
   556  	if len(tSet) != len(parents) {
   557  		t.Fatal("parents should have same length as unconfirmed transaction set")
   558  	}
   559  	for i := 0; i < len(tSet); i++ {
   560  		if tSet[i].ID() != parents[i].ID() {
   561  			t.Error("returned parent doesn't match transaction of transaction set")
   562  		}
   563  	}
   564  }