github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/transactionpool/accept_test.go (about)

     1  package transactionpool
     2  
     3  import (
     4  	"crypto/rand"
     5  	"testing"
     6  
     7  	"github.com/NebulousLabs/Sia/modules"
     8  	"github.com/NebulousLabs/Sia/types"
     9  )
    10  
    11  // TestIntegrationAcceptTransactionSet probes the AcceptTransactionSet method
    12  // of the transaction pool.
    13  func TestIntegrationAcceptTransactionSet(t *testing.T) {
    14  	// Create a transaction pool tester.
    15  	tpt, err := createTpoolTester("TestIntegrationAcceptTransactionSet")
    16  	if err != nil {
    17  		t.Fatal(err)
    18  	}
    19  
    20  	// Check that the transaction pool is empty.
    21  	if len(tpt.tpool.transactionSets) != 0 {
    22  		t.Error("transaction pool is not empty")
    23  	}
    24  
    25  	// Create a valid transaction set using the wallet.
    26  	txns, err := tpt.wallet.SendSiacoins(types.NewCurrency64(100), types.UnlockHash{})
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	if len(tpt.tpool.transactionSets) != 1 {
    31  		t.Error("sending coins did not increase the transaction sets by 1")
    32  	}
    33  
    34  	// Submit the transaction set again to trigger a duplication error.
    35  	err = tpt.tpool.AcceptTransactionSet(txns)
    36  	if err != modules.ErrDuplicateTransactionSet {
    37  		t.Error(err)
    38  	}
    39  
    40  	// Mine a block and check that the transaction pool gets emptied.
    41  	block, _ := tpt.miner.FindBlock()
    42  	err = tpt.cs.AcceptBlock(block)
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	if len(tpt.tpool.TransactionList()) != 0 {
    47  		t.Error("transaction pool was not emptied after mining a block")
    48  	}
    49  
    50  	// Try to resubmit the transaction set to verify
    51  	err = tpt.tpool.AcceptTransactionSet(txns)
    52  	if err == nil {
    53  		t.Error("transaction set was supposed to be rejected")
    54  	}
    55  }
    56  
    57  // TestIntegrationConflictingTransactionSets tries to add two transaction sets
    58  // to the transaction pool that are each legal individually, but double spend
    59  // an output.
    60  func TestIntegrationConflictingTransactionSets(t *testing.T) {
    61  	if testing.Short() {
    62  		t.SkipNow()
    63  	}
    64  	// Create a transaction pool tester.
    65  	tpt, err := createTpoolTester("TestIntegrationConflictingTransactionSets")
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	// Fund a partial transaction.
    71  	fund := types.NewCurrency64(30e6)
    72  	txnBuilder := tpt.wallet.StartTransaction()
    73  	err = txnBuilder.FundSiacoins(fund)
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  	// wholeTransaction is set to false so that we can use the same signature
    78  	// to create a double spend.
    79  	txnSet, err := txnBuilder.Sign(false)
    80  	if err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	txnSetDoubleSpend := make([]types.Transaction, len(txnSet))
    84  	copy(txnSetDoubleSpend, txnSet)
    85  
    86  	// There are now two sets of transactions that are signed and ready to
    87  	// spend the same output. Have one spend the money in a miner fee, and the
    88  	// other create a siacoin output.
    89  	txnIndex := len(txnSet) - 1
    90  	txnSet[txnIndex].MinerFees = append(txnSet[txnIndex].MinerFees, fund)
    91  	txnSetDoubleSpend[txnIndex].SiacoinOutputs = append(txnSetDoubleSpend[txnIndex].SiacoinOutputs, types.SiacoinOutput{Value: fund})
    92  
    93  	// Add the first and then the second txn set.
    94  	err = tpt.tpool.AcceptTransactionSet(txnSet)
    95  	if err != nil {
    96  		t.Error(err)
    97  	}
    98  	err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend)
    99  	if err == nil {
   100  		t.Error("transaction should not have passed inspection")
   101  	}
   102  
   103  	// Purge and try the sets in the reverse order.
   104  	tpt.tpool.PurgeTransactionPool()
   105  	err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend)
   106  	if err != nil {
   107  		t.Error(err)
   108  	}
   109  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   110  	if err == nil {
   111  		t.Error("transaction should not have passed inspection")
   112  	}
   113  }
   114  
   115  // TestIntegrationCheckMinerFees probes the checkMinerFees method of the
   116  // transaction pool.
   117  func TestIntegrationCheckMinerFees(t *testing.T) {
   118  	if testing.Short() {
   119  		t.SkipNow()
   120  	}
   121  	// Create a transaction pool tester.
   122  	tpt, err := createTpoolTester("TestIntegrationCheckMinerFees")
   123  	if err != nil {
   124  		t.Fatal(err)
   125  	}
   126  
   127  	// Fill the transaction pool to the fee limit.
   128  	for i := 0; i < TransactionPoolSizeForFee/10e3; i++ {
   129  		arbData := make([]byte, 10e3)
   130  		copy(arbData, modules.PrefixNonSia[:])
   131  		_, err = rand.Read(arbData[100:116]) // prevents collisions with other transacitons in the loop.
   132  		if err != nil {
   133  			t.Fatal(err)
   134  		}
   135  		txn := types.Transaction{ArbitraryData: [][]byte{arbData}}
   136  		err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn})
   137  		if err != nil {
   138  			t.Fatal(err)
   139  		}
   140  	}
   141  
   142  	// Add another transaction, this one should fail for having too few fees.
   143  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{{}})
   144  	if err != errLowMinerFees {
   145  		t.Error(err)
   146  	}
   147  
   148  	// Add a transaction that has sufficient fees.
   149  	_, err = tpt.wallet.SendSiacoins(types.NewCurrency64(100), types.UnlockHash{})
   150  	if err != nil {
   151  		t.Error(err)
   152  	}
   153  
   154  	// TODO: fill the pool up all the way and try again.
   155  }
   156  
   157  // TestTransactionSuperset submits a single transaction to the network,
   158  // followed by a transaction set containing that single transaction.
   159  func TestIntegrationTransactionSuperset(t *testing.T) {
   160  	if testing.Short() {
   161  		t.SkipNow()
   162  	}
   163  	// Create a transaction pool tester.
   164  	tpt, err := createTpoolTester("TestTransactionSuperset")
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  
   169  	// Fund a partial transaction.
   170  	fund := types.NewCurrency64(30e6)
   171  	txnBuilder := tpt.wallet.StartTransaction()
   172  	err = txnBuilder.FundSiacoins(fund)
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	txnBuilder.AddMinerFee(fund)
   177  	// wholeTransaction is set to false so that we can use the same signature
   178  	// to create a double spend.
   179  	txnSet, err := txnBuilder.Sign(false)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  	if len(txnSet) <= 1 {
   184  		t.Fatal("test is invalid unless the transaction set has two or more transactions")
   185  	}
   186  	// Check that the second transaction is dependent on the first.
   187  	err = tpt.tpool.AcceptTransactionSet(txnSet[1:])
   188  	if err == nil {
   189  		t.Fatal("transaction set must have dependent transactions")
   190  	}
   191  
   192  	// Submit the first transaction in the set to the transaction pool, and
   193  	// then the superset.
   194  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   195  	if err != nil {
   196  		t.Fatal("first transaction in the transaction set was not valid?")
   197  	}
   198  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   199  	if err != nil {
   200  		t.Fatal("super setting is not working:", err)
   201  	}
   202  
   203  	// Try resubmitting the individual transaction and the superset, a
   204  	// duplication error should be returned for each case.
   205  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   206  	if err != modules.ErrDuplicateTransactionSet {
   207  		t.Fatal(err)
   208  	}
   209  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   210  	if err != modules.ErrDuplicateTransactionSet {
   211  		t.Fatal("super setting is not working:", err)
   212  	}
   213  }
   214  
   215  // TestTransactionSubset submits a transaction set to the network, followed by
   216  // just a subset, expectint ErrDuplicateTransactionSet as a response.
   217  func TestIntegrationTransactionSubset(t *testing.T) {
   218  	if testing.Short() {
   219  		t.SkipNow()
   220  	}
   221  	// Create a transaction pool tester.
   222  	tpt, err := createTpoolTester("TestTransactionSubset")
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  
   227  	// Fund a partial transaction.
   228  	fund := types.NewCurrency64(30e6)
   229  	txnBuilder := tpt.wallet.StartTransaction()
   230  	err = txnBuilder.FundSiacoins(fund)
   231  	if err != nil {
   232  		t.Fatal(err)
   233  	}
   234  	txnBuilder.AddMinerFee(fund)
   235  	// wholeTransaction is set to false so that we can use the same signature
   236  	// to create a double spend.
   237  	txnSet, err := txnBuilder.Sign(false)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	if len(txnSet) <= 1 {
   242  		t.Fatal("test is invalid unless the transaction set has two or more transactions")
   243  	}
   244  	// Check that the second transaction is dependent on the first.
   245  	err = tpt.tpool.AcceptTransactionSet(txnSet[1:])
   246  	if err == nil {
   247  		t.Fatal("transaction set must have dependent transactions")
   248  	}
   249  
   250  	// Submit the set to the pool, followed by just the transaction.
   251  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   252  	if err != nil {
   253  		t.Fatal("super setting is not working:", err)
   254  	}
   255  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   256  	if err != modules.ErrDuplicateTransactionSet {
   257  		t.Fatal(err)
   258  	}
   259  }
   260  
   261  // TestIntegrationTransactionChild submits a single transaction to the network,
   262  // followed by a child transaction.
   263  func TestIntegrationTransactionChild(t *testing.T) {
   264  	if testing.Short() {
   265  		t.SkipNow()
   266  	}
   267  	// Create a transaction pool tester.
   268  	tpt, err := createTpoolTester("TestTransactionChild")
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	// Fund a partial transaction.
   274  	fund := types.NewCurrency64(30e6)
   275  	txnBuilder := tpt.wallet.StartTransaction()
   276  	err = txnBuilder.FundSiacoins(fund)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	txnBuilder.AddMinerFee(fund)
   281  	// wholeTransaction is set to false so that we can use the same signature
   282  	// to create a double spend.
   283  	txnSet, err := txnBuilder.Sign(false)
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	if len(txnSet) <= 1 {
   288  		t.Fatal("test is invalid unless the transaction set has two or more transactions")
   289  	}
   290  	// Check that the second transaction is dependent on the first.
   291  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txnSet[1]})
   292  	if err == nil {
   293  		t.Fatal("transaction set must have dependent transactions")
   294  	}
   295  
   296  	// Submit the first transaction in the set to the transaction pool.
   297  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   298  	if err != nil {
   299  		t.Fatal("first transaction in the transaction set was not valid?")
   300  	}
   301  	err = tpt.tpool.AcceptTransactionSet(txnSet[1:])
   302  	if err != nil {
   303  		t.Fatal("child transaction not seen as valid")
   304  	}
   305  }
   306  
   307  // TestIntegrationNilAccept tries submitting a nil transaction set and a 0-len
   308  // transaction set to the transaction pool.
   309  func TestIntegrationNilAccept(t *testing.T) {
   310  	if testing.Short() {
   311  		t.SkipNow()
   312  	}
   313  	tpt, err := createTpoolTester("TestTransactionChild")
   314  	if err != nil {
   315  		t.Fatal(err)
   316  	}
   317  
   318  	err = tpt.tpool.AcceptTransactionSet(nil)
   319  	if err == nil {
   320  		t.Error("no error returned when submitting nothing to the transaction pool")
   321  	}
   322  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{})
   323  	if err == nil {
   324  		t.Error("no error returned when submitting nothing to the transaction pool")
   325  	}
   326  }
   327  
   328  // TestAcceptFCAndConflictingRevision checks that the transaction pool
   329  // correctly accepts a file contract in a transaction set followed by a correct
   330  // revision to that file contract in the a following transaction set, with no
   331  // block separating them.
   332  func TestAcceptFCAndConflictingRevision(t *testing.T) {
   333  	if testing.Short() {
   334  		t.SkipNow()
   335  	}
   336  	tpt, err := createTpoolTester("TestAcceptFCAndConflictingRevision")
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  
   341  	// Create and fund a valid file contract.
   342  	builder := tpt.wallet.StartTransaction()
   343  	payout := types.NewCurrency64(1e9)
   344  	err = builder.FundSiacoins(payout)
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	}
   348  	builder.AddFileContract(types.FileContract{
   349  		WindowStart:        tpt.cs.Height() + 2,
   350  		WindowEnd:          tpt.cs.Height() + 5,
   351  		Payout:             payout,
   352  		ValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   353  		MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   354  		UnlockHash:         types.UnlockConditions{}.UnlockHash(),
   355  	})
   356  	tSet, err := builder.Sign(true)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	err = tpt.tpool.AcceptTransactionSet(tSet)
   361  	if err != nil {
   362  		t.Fatal(err)
   363  	}
   364  	fcid := tSet[len(tSet)-1].FileContractID(0)
   365  
   366  	// Create a file contract revision and submit it.
   367  	rSet := []types.Transaction{{
   368  		FileContractRevisions: []types.FileContractRevision{{
   369  			ParentID:          fcid,
   370  			NewRevisionNumber: 2,
   371  
   372  			NewWindowStart:        tpt.cs.Height() + 2,
   373  			NewWindowEnd:          tpt.cs.Height() + 5,
   374  			NewValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   375  			NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   376  		}},
   377  	}}
   378  	err = tpt.tpool.AcceptTransactionSet(rSet)
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  }
   383  
   384  // TestPartialConfirmation checks that the transaction pool correctly accepts a
   385  // transaction set which has parents that have been accepted by the consensus
   386  // set but not the whole set has been accepted by the consensus set.
   387  func TestPartialConfirmation(t *testing.T) {
   388  	if testing.Short() {
   389  		t.SkipNow()
   390  	}
   391  	tpt, err := createTpoolTester("TestPartialConfirmation")
   392  	if err != nil {
   393  		t.Fatal(err)
   394  	}
   395  
   396  	// Create and fund a valid file contract.
   397  	builder := tpt.wallet.StartTransaction()
   398  	payout := types.NewCurrency64(1e9)
   399  	err = builder.FundSiacoins(payout)
   400  	if err != nil {
   401  		t.Fatal(err)
   402  	}
   403  	builder.AddFileContract(types.FileContract{
   404  		WindowStart:        tpt.cs.Height() + 2,
   405  		WindowEnd:          tpt.cs.Height() + 5,
   406  		Payout:             payout,
   407  		ValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   408  		MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   409  		UnlockHash:         types.UnlockConditions{}.UnlockHash(),
   410  	})
   411  	tSet, err := builder.Sign(true)
   412  	if err != nil {
   413  		t.Fatal(err)
   414  	}
   415  	fcid := tSet[len(tSet)-1].FileContractID(0)
   416  
   417  	// Create a file contract revision.
   418  	rSet := []types.Transaction{{
   419  		FileContractRevisions: []types.FileContractRevision{{
   420  			ParentID:          fcid,
   421  			NewRevisionNumber: 2,
   422  
   423  			NewWindowStart:        tpt.cs.Height() + 2,
   424  			NewWindowEnd:          tpt.cs.Height() + 5,
   425  			NewValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   426  			NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   427  		}},
   428  	}}
   429  
   430  	// Combine the contract and revision in to a single set.
   431  	fullSet := append(tSet, rSet...)
   432  
   433  	// Get the tSet onto the blockchain.
   434  	unsolvedBlock, target, err := tpt.miner.BlockForWork()
   435  	if err != nil {
   436  		t.Fatal(err)
   437  	}
   438  	unsolvedBlock.Transactions = append(unsolvedBlock.Transactions, tSet...)
   439  	solvedBlock, solved := tpt.miner.SolveBlock(unsolvedBlock, target)
   440  	if !solved {
   441  		t.Fatal("Failed to solve block")
   442  	}
   443  	err = tpt.cs.AcceptBlock(solvedBlock)
   444  	if err != nil {
   445  		t.Fatal(err)
   446  	}
   447  
   448  	// Try to get the full set into the transaction pool. The transaction pool
   449  	// should recognize that the set is partially accepted, and be able to
   450  	// accept on the the transactions that are new and are not yet on the
   451  	// blockchain.
   452  	err = tpt.tpool.AcceptTransactionSet(fullSet)
   453  	if err != nil {
   454  		t.Fatal(err)
   455  	}
   456  }
   457  
   458  // TestPartialConfirmationWeave checks that the transaction pool correctly
   459  // accepts a transaction set which has parents that have been accepted by the
   460  // consensus set but not the whole set has been accepted by the consensus set,
   461  // this time weaving the dependencies, such that the first transaction is not
   462  // in the consensus set, the second is, and the third has both as dependencies.
   463  func TestPartialConfirmationWeave(t *testing.T) {
   464  	if testing.Short() {
   465  		t.SkipNow()
   466  	}
   467  	tpt, err := createTpoolTester("TestPartialConfirmation")
   468  	if err != nil {
   469  		t.Fatal(err)
   470  	}
   471  
   472  	// Step 1: create an output to the empty address in a tx.
   473  	// Step 2: create a second output to the empty address in another tx.
   474  	// Step 3: create a transaction using both those outputs.
   475  	// Step 4: mine the txn set in step 2
   476  	// Step 5: Submit the complete set.
   477  
   478  	// Create a transaction with a single output to a fully controlled address.
   479  	emptyUH := types.UnlockConditions{}.UnlockHash()
   480  	builder1 := tpt.wallet.StartTransaction()
   481  	funding1 := types.NewCurrency64(1e9)
   482  	err = builder1.FundSiacoins(funding1)
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  	scOutput1 := types.SiacoinOutput{
   487  		Value:      funding1,
   488  		UnlockHash: emptyUH,
   489  	}
   490  	i1 := builder1.AddSiacoinOutput(scOutput1)
   491  	tSet1, err := builder1.Sign(true)
   492  	if err != nil {
   493  		t.Fatal(err)
   494  	}
   495  	// Submit to the transaction pool and mine the block, to minimize
   496  	// complexity.
   497  	err = tpt.tpool.AcceptTransactionSet(tSet1)
   498  	if err != nil {
   499  		t.Fatal(err)
   500  	}
   501  	_, err = tpt.miner.AddBlock()
   502  	if err != nil {
   503  		t.Fatal(err)
   504  	}
   505  
   506  	// Create a second output to the fully controlled address, to fund the
   507  	// second transaction in the weave.
   508  	builder2 := tpt.wallet.StartTransaction()
   509  	funding2 := types.NewCurrency64(2e9)
   510  	err = builder2.FundSiacoins(funding2)
   511  	if err != nil {
   512  		t.Fatal(err)
   513  	}
   514  	scOutput2 := types.SiacoinOutput{
   515  		Value:      funding2,
   516  		UnlockHash: emptyUH,
   517  	}
   518  	i2 := builder2.AddSiacoinOutput(scOutput2)
   519  	tSet2, err := builder2.Sign(true)
   520  	if err != nil {
   521  		t.Fatal(err)
   522  	}
   523  	// Submit to the transaction pool and mine the block, to minimize
   524  	// complexity.
   525  	err = tpt.tpool.AcceptTransactionSet(tSet2)
   526  	if err != nil {
   527  		t.Fatal(err)
   528  	}
   529  	_, err = tpt.miner.AddBlock()
   530  	if err != nil {
   531  		t.Fatal(err)
   532  	}
   533  
   534  	// Create a passthrough transaction for output1 and output2, so that they
   535  	// can be used as unconfirmed dependencies.
   536  	txn1 := types.Transaction{
   537  		SiacoinInputs: []types.SiacoinInput{{
   538  			ParentID: tSet1[len(tSet1)-1].SiacoinOutputID(i1),
   539  		}},
   540  		SiacoinOutputs: []types.SiacoinOutput{{
   541  			Value:      funding1,
   542  			UnlockHash: emptyUH,
   543  		}},
   544  	}
   545  	txn2 := types.Transaction{
   546  		SiacoinInputs: []types.SiacoinInput{{
   547  			ParentID: tSet2[len(tSet2)-1].SiacoinOutputID(i2),
   548  		}},
   549  		SiacoinOutputs: []types.SiacoinOutput{{
   550  			Value:      funding2,
   551  			UnlockHash: emptyUH,
   552  		}},
   553  	}
   554  
   555  	// Create a child transaction that depends on inputs from both txn1 and
   556  	// txn2.
   557  	child := types.Transaction{
   558  		SiacoinInputs: []types.SiacoinInput{
   559  			{
   560  				ParentID: txn1.SiacoinOutputID(0),
   561  			},
   562  			{
   563  				ParentID: txn2.SiacoinOutputID(0),
   564  			},
   565  		},
   566  		SiacoinOutputs: []types.SiacoinOutput{{
   567  			Value: funding1.Add(funding2),
   568  		}},
   569  	}
   570  
   571  	// Get txn2 accepted into the consensus set.
   572  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn2})
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  	_, err = tpt.miner.AddBlock()
   577  	if err != nil {
   578  		t.Fatal(err)
   579  	}
   580  
   581  	// Try to get the set of txn1, txn2, and child accepted into the
   582  	// transaction pool.
   583  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn1, txn2, child})
   584  	if err != nil {
   585  		t.Fatal(err)
   586  	}
   587  }