gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/transactionpool/accept_test.go (about)

     1  package transactionpool
     2  
     3  import (
     4  	"testing"
     5  
     6  	"gitlab.com/NebulousLabs/fastrand"
     7  	"gitlab.com/SiaPrime/SiaPrime/modules"
     8  	"gitlab.com/SiaPrime/SiaPrime/types"
     9  )
    10  
    11  // TestAcceptTransactionSet probes the AcceptTransactionSet method
    12  // of the transaction pool.
    13  func TestAcceptTransactionSet(t *testing.T) {
    14  	if testing.Short() {
    15  		t.SkipNow()
    16  	}
    17  	// Create a transaction pool tester.
    18  	tpt, err := createTpoolTester(t.Name())
    19  	if err != nil {
    20  		t.Fatal(err)
    21  	}
    22  	defer tpt.Close()
    23  
    24  	// Check that the transaction pool is empty.
    25  	if len(tpt.tpool.transactionSets) != 0 {
    26  		t.Error("transaction pool is not empty")
    27  	}
    28  
    29  	// Create a valid transaction set using the wallet.
    30  	txns, err := tpt.wallet.SendSiacoins(types.NewCurrency64(100), types.UnlockHash{})
    31  	if err != nil {
    32  		t.Fatal(err)
    33  	}
    34  	if len(tpt.tpool.transactionSets) != 1 {
    35  		t.Error("sending coins did not increase the transaction sets by 1")
    36  	}
    37  
    38  	// Submit the transaction set again to trigger a duplication error.
    39  	err = tpt.tpool.AcceptTransactionSet(txns)
    40  	if err != modules.ErrDuplicateTransactionSet {
    41  		t.Error(err)
    42  	}
    43  
    44  	// Mine a block and check that the transaction pool gets emptied.
    45  	block, _ := tpt.miner.FindBlock()
    46  	err = tpt.cs.AcceptBlock(block)
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  	if len(tpt.tpool.TransactionList()) != 0 {
    51  		t.Error("transaction pool was not emptied after mining a block")
    52  	}
    53  
    54  	// Try to resubmit the transaction set to verify
    55  	err = tpt.tpool.AcceptTransactionSet(txns)
    56  	if err == nil {
    57  		t.Error("transaction set was supposed to be rejected")
    58  	}
    59  }
    60  
    61  // TestConflictingTransactionSets tries to add two transaction sets
    62  // to the transaction pool that are each legal individually, but double spend
    63  // an output.
    64  func TestConflictingTransactionSets(t *testing.T) {
    65  	if testing.Short() {
    66  		t.SkipNow()
    67  	}
    68  	// Create a transaction pool tester.
    69  	tpt, err := createTpoolTester(t.Name())
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	defer tpt.Close()
    74  
    75  	// Fund a partial transaction.
    76  	fund := types.NewCurrency64(30e6)
    77  	txnBuilder, err := tpt.wallet.StartTransaction()
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	err = txnBuilder.FundSiacoins(fund)
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	// wholeTransaction is set to false so that we can use the same signature
    86  	// to create a double spend.
    87  	txnSet, err := txnBuilder.Sign(false)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	txnSetDoubleSpend := make([]types.Transaction, len(txnSet))
    92  	copy(txnSetDoubleSpend, txnSet)
    93  
    94  	// There are now two sets of transactions that are signed and ready to
    95  	// spend the same output. Have one spend the money in a miner fee, and the
    96  	// other create a siacoin output.
    97  	txnIndex := len(txnSet) - 1
    98  	txnSet[txnIndex].MinerFees = append(txnSet[txnIndex].MinerFees, fund)
    99  	txnSetDoubleSpend[txnIndex].SiacoinOutputs = append(txnSetDoubleSpend[txnIndex].SiacoinOutputs, types.SiacoinOutput{Value: fund})
   100  
   101  	// Add the first and then the second txn set.
   102  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   103  	if err != nil {
   104  		t.Error(err)
   105  	}
   106  	err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend)
   107  	if err == nil {
   108  		t.Error("transaction should not have passed inspection")
   109  	}
   110  
   111  	// Purge and try the sets in the reverse order.
   112  	tpt.tpool.PurgeTransactionPool()
   113  	err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend)
   114  	if err != nil {
   115  		t.Error(err)
   116  	}
   117  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   118  	if err == nil {
   119  		t.Error("transaction should not have passed inspection")
   120  	}
   121  }
   122  
   123  // TestCheckMinerFees probes the checkMinerFees method of the
   124  // transaction pool.
   125  func TestCheckMinerFees(t *testing.T) {
   126  	if testing.Short() {
   127  		t.SkipNow()
   128  	}
   129  	// Create a transaction pool tester.
   130  	tpt, err := createTpoolTester(t.Name())
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	defer tpt.Close()
   135  
   136  	// Prepare a bunch of outputs for a series of graphs to fill up the
   137  	// transaction pool.
   138  	graphLens := 200                                                          // 40 kb per graph
   139  	numGraphs := (int(TransactionPoolSizeTarget) * 4 / 3) / (graphLens * 206) // 206 is the size of a single input-output graph txn.
   140  	graphFund := types.SiacoinPrecision.Mul64(1000)
   141  	var outputs []types.SiacoinOutput
   142  	for i := 0; i < numGraphs+1; i++ {
   143  		outputs = append(outputs, types.SiacoinOutput{
   144  			UnlockHash: types.UnlockConditions{}.UnlockHash(),
   145  			Value:      graphFund,
   146  		})
   147  	}
   148  	txns, err := tpt.wallet.SendSiacoinsMulti(outputs)
   149  	if err != nil {
   150  		t.Error(err)
   151  	}
   152  
   153  	// Mine the graph setup in the consensus set so that the graph outputs are
   154  	// distinct transaction sets.
   155  	_, err = tpt.miner.AddBlock()
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	// Recommended fees at this point should be the minimum.
   161  	minRec, maxRec := tpt.tpool.FeeEstimation()
   162  	if minRec.Cmp(minEstimation) < 0 {
   163  		t.Error("transaction pool is not respecting the sane fee minimum")
   164  	}
   165  	if maxRec.Cmp(minEstimation.Mul64(3)) < 0 {
   166  		t.Error("transaction pool is not respecting the sane fee min maximum")
   167  	}
   168  
   169  	// Fill the transaction pool to the fee limit.
   170  	for i := 0; i < TransactionPoolSizeForFee/10e3; i++ {
   171  		arbData := make([]byte, 10e3)
   172  		copy(arbData, modules.PrefixNonSia[:])
   173  		fastrand.Read(arbData[100:116]) // prevents collisions with other transacitons in the loop.
   174  		txn := types.Transaction{ArbitraryData: [][]byte{arbData}}
   175  		err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn})
   176  		if err != nil {
   177  			t.Fatal(err)
   178  		}
   179  	}
   180  
   181  	// Add another transaction, this one should fail for having too few fees.
   182  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{{}})
   183  	if err != errLowMinerFees {
   184  		t.Error(err)
   185  	}
   186  
   187  	// Add a transaction that has sufficient fees.
   188  	_, err = tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(50), types.UnlockHash{})
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  
   193  	// Create all of the graphs.
   194  	finalTxn := txns[len(txns)-1]
   195  	for i := 0; i < numGraphs; i++ {
   196  		var edges []types.TransactionGraphEdge
   197  		for j := 0; j < graphLens; j++ {
   198  			edges = append(edges, types.TransactionGraphEdge{
   199  				Dest:   j + 1,
   200  				Fee:    types.SiacoinPrecision,
   201  				Source: j,
   202  				Value:  graphFund.Sub(types.SiacoinPrecision.Mul64(uint64(j + 1))),
   203  			})
   204  		}
   205  		graph, err := types.TransactionGraph(finalTxn.SiacoinOutputID(uint64(i)), edges)
   206  		if err != nil {
   207  			t.Fatal(err)
   208  		}
   209  		err = tpt.tpool.AcceptTransactionSet(graph)
   210  		if err != nil {
   211  			t.Fatal(err)
   212  		}
   213  	}
   214  
   215  	// Try to submit a transaction with too few fees.
   216  	source := finalTxn.SiacoinOutputID(uint64(numGraphs))
   217  	lowFee := types.SiacoinPrecision.Div64(3)
   218  	remaining := types.SiacoinPrecision.Mul64(1000).Sub(lowFee)
   219  	edge := types.TransactionGraphEdge{
   220  		Dest:   1,
   221  		Fee:    lowFee,
   222  		Source: 0,
   223  		Value:  remaining,
   224  	}
   225  	lowFeeGraph, err := types.TransactionGraph(source, []types.TransactionGraphEdge{edge})
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	err = tpt.tpool.AcceptTransactionSet(lowFeeGraph)
   230  	if err != errLowMinerFees {
   231  		t.Fatal(err)
   232  	}
   233  }
   234  
   235  // TestTransactionGraph checks that the TransactionGraph method of the types
   236  // package is able to create transasctions that actually validate and can get
   237  // inserted into the tpool.
   238  func TestTransactionGraph(t *testing.T) {
   239  	if testing.Short() {
   240  		t.SkipNow()
   241  	}
   242  	// Create a transaction pool tester.
   243  	tpt, err := createTpoolTester(t.Name())
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  	defer tpt.Close()
   248  
   249  	// Create a transaction sending money to an output that TransactionGraph can
   250  	// spent (the empty UnlockConditions).
   251  	txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(100), types.UnlockConditions{}.UnlockHash())
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  
   256  	// Get the output of that transaction.
   257  	graphSourceOutputID := txns[len(txns)-1].SiacoinOutputID(0)
   258  	edge := types.TransactionGraphEdge{
   259  		Dest:   1,
   260  		Fee:    types.SiacoinPrecision.Mul64(10),
   261  		Source: 0,
   262  		Value:  types.SiacoinPrecision.Mul64(90),
   263  	}
   264  	graphTxns, err := types.TransactionGraph(graphSourceOutputID, []types.TransactionGraphEdge{edge})
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	if len(graphTxns) != 1 {
   269  		t.Fatal("wrong number of tranasctions produced")
   270  	}
   271  	err = tpt.tpool.AcceptTransactionSet(graphTxns)
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  }
   276  
   277  // TestTransactionGraphDiamond checks that the TransactionGraph method of the
   278  // types package is able to create transasctions that actually validate and can
   279  // get inserted into the tpool.
   280  func TestTransactionGraphDiamond(t *testing.T) {
   281  	if testing.Short() {
   282  		t.SkipNow()
   283  	}
   284  	// Create a transaction pool tester.
   285  	tpt, err := createTpoolTester(t.Name())
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	defer tpt.Close()
   290  
   291  	// Create a transaction sending money to an output that TransactionGraph can
   292  	// spent (the empty UnlockConditions).
   293  	txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(100), types.UnlockConditions{}.UnlockHash())
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  
   298  	// Get the output of that transaction.
   299  	graphSourceOutputID := txns[len(txns)-1].SiacoinOutputID(0)
   300  	var edges []types.TransactionGraphEdge
   301  	sources := []int{0, 0, 1, 2}
   302  	dests := []int{1, 2, 3, 3}
   303  	values := []uint64{40, 40, 30, 30}
   304  	fees := []uint64{10, 10, 10, 10}
   305  	for i := range sources {
   306  		edges = append(edges, types.TransactionGraphEdge{
   307  			Dest:   dests[i],
   308  			Fee:    types.SiacoinPrecision.Mul64(fees[i]),
   309  			Source: sources[i],
   310  			Value:  types.SiacoinPrecision.Mul64(values[i]),
   311  		})
   312  	}
   313  	graphTxns, err := types.TransactionGraph(graphSourceOutputID, edges)
   314  	if err != nil {
   315  		t.Fatal(err)
   316  	}
   317  	if len(graphTxns) != 3 {
   318  		t.Fatal("wrong number of tranasctions produced")
   319  	}
   320  	err = tpt.tpool.AcceptTransactionSet(graphTxns)
   321  	if err != nil {
   322  		t.Fatal(err)
   323  	}
   324  }
   325  
   326  // TestTransactionSuperset submits a single transaction to the network,
   327  // followed by a transaction set containing that single transaction.
   328  func TestTransactionSuperset(t *testing.T) {
   329  	if testing.Short() {
   330  		t.SkipNow()
   331  	}
   332  	// Create a transaction pool tester.
   333  	tpt, err := createTpoolTester(t.Name())
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	defer tpt.Close()
   338  
   339  	// Fund a partial transaction.
   340  	fund := types.NewCurrency64(30e6)
   341  	txnBuilder, err := tpt.wallet.StartTransaction()
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  	err = txnBuilder.FundSiacoins(fund)
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	txnBuilder.AddMinerFee(fund)
   350  	// wholeTransaction is set to false so that we can use the same signature
   351  	// to create a double spend.
   352  	txnSet, err := txnBuilder.Sign(false)
   353  	if err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	if len(txnSet) <= 1 {
   357  		t.Fatal("test is invalid unless the transaction set has two or more transactions")
   358  	}
   359  	// Check that the second transaction is dependent on the first.
   360  	err = tpt.tpool.AcceptTransactionSet(txnSet[1:])
   361  	if err == nil {
   362  		t.Fatal("transaction set must have dependent transactions")
   363  	}
   364  
   365  	// Submit the first transaction in the set to the transaction pool, and
   366  	// then the superset.
   367  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   368  	if err != nil {
   369  		t.Fatal("first transaction in the transaction set was not valid?")
   370  	}
   371  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   372  	if err != nil {
   373  		t.Fatal("super setting is not working:", err)
   374  	}
   375  
   376  	// Try resubmitting the individual transaction and the superset, a
   377  	// duplication error should be returned for each case.
   378  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   379  	if err != modules.ErrDuplicateTransactionSet {
   380  		t.Fatal(err)
   381  	}
   382  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   383  	if err != modules.ErrDuplicateTransactionSet {
   384  		t.Fatal("super setting is not working:", err)
   385  	}
   386  }
   387  
   388  // TestTransactionSubset submits a transaction set to the network, followed by
   389  // just a subset, expectint ErrDuplicateTransactionSet as a response.
   390  func TestTransactionSubset(t *testing.T) {
   391  	if testing.Short() {
   392  		t.SkipNow()
   393  	}
   394  	// Create a transaction pool tester.
   395  	tpt, err := createTpoolTester(t.Name())
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	defer tpt.Close()
   400  
   401  	// Fund a partial transaction.
   402  	fund := types.NewCurrency64(30e6)
   403  	txnBuilder, err := tpt.wallet.StartTransaction()
   404  	if err != nil {
   405  		t.Fatal(err)
   406  	}
   407  	err = txnBuilder.FundSiacoins(fund)
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  	txnBuilder.AddMinerFee(fund)
   412  	// wholeTransaction is set to false so that we can use the same signature
   413  	// to create a double spend.
   414  	txnSet, err := txnBuilder.Sign(false)
   415  	if err != nil {
   416  		t.Fatal(err)
   417  	}
   418  	if len(txnSet) <= 1 {
   419  		t.Fatal("test is invalid unless the transaction set has two or more transactions")
   420  	}
   421  	// Check that the second transaction is dependent on the first.
   422  	err = tpt.tpool.AcceptTransactionSet(txnSet[1:])
   423  	if err == nil {
   424  		t.Fatal("transaction set must have dependent transactions")
   425  	}
   426  
   427  	// Submit the set to the pool, followed by just the transaction.
   428  	err = tpt.tpool.AcceptTransactionSet(txnSet)
   429  	if err != nil {
   430  		t.Fatal("super setting is not working:", err)
   431  	}
   432  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   433  	if err != modules.ErrDuplicateTransactionSet {
   434  		t.Fatal(err)
   435  	}
   436  }
   437  
   438  // TestTransactionChild submits a single transaction to the network,
   439  // followed by a child transaction.
   440  func TestTransactionChild(t *testing.T) {
   441  	if testing.Short() {
   442  		t.SkipNow()
   443  	}
   444  	// Create a transaction pool tester.
   445  	tpt, err := createTpoolTester(t.Name())
   446  	if err != nil {
   447  		t.Fatal(err)
   448  	}
   449  	defer tpt.Close()
   450  
   451  	// Fund a partial transaction.
   452  	fund := types.NewCurrency64(30e6)
   453  	txnBuilder, err := tpt.wallet.StartTransaction()
   454  	if err != nil {
   455  		t.Fatal(err)
   456  	}
   457  	err = txnBuilder.FundSiacoins(fund)
   458  	if err != nil {
   459  		t.Fatal(err)
   460  	}
   461  	txnBuilder.AddMinerFee(fund)
   462  	// wholeTransaction is set to false so that we can use the same signature
   463  	// to create a double spend.
   464  	txnSet, err := txnBuilder.Sign(false)
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	if len(txnSet) <= 1 {
   469  		t.Fatal("test is invalid unless the transaction set has two or more transactions")
   470  	}
   471  	// Check that the second transaction is dependent on the first.
   472  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txnSet[1]})
   473  	if err == nil {
   474  		t.Fatal("transaction set must have dependent transactions")
   475  	}
   476  
   477  	// Submit the first transaction in the set to the transaction pool.
   478  	err = tpt.tpool.AcceptTransactionSet(txnSet[:1])
   479  	if err != nil {
   480  		t.Fatal("first transaction in the transaction set was not valid?")
   481  	}
   482  	err = tpt.tpool.AcceptTransactionSet(txnSet[1:])
   483  	if err != nil {
   484  		t.Fatal("child transaction not seen as valid")
   485  	}
   486  }
   487  
   488  // TestNilAccept tries submitting a nil transaction set and a 0-len
   489  // transaction set to the transaction pool.
   490  func TestNilAccept(t *testing.T) {
   491  	if testing.Short() {
   492  		t.SkipNow()
   493  	}
   494  	tpt, err := createTpoolTester(t.Name())
   495  	if err != nil {
   496  		t.Fatal(err)
   497  	}
   498  	defer tpt.Close()
   499  
   500  	err = tpt.tpool.AcceptTransactionSet(nil)
   501  	if err == nil {
   502  		t.Error("no error returned when submitting nothing to the transaction pool")
   503  	}
   504  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{})
   505  	if err == nil {
   506  		t.Error("no error returned when submitting nothing to the transaction pool")
   507  	}
   508  }
   509  
   510  // TestAcceptFCAndConflictingRevision checks that the transaction pool
   511  // correctly accepts a file contract in a transaction set followed by a correct
   512  // revision to that file contract in the a following transaction set, with no
   513  // block separating them.
   514  func TestAcceptFCAndConflictingRevision(t *testing.T) {
   515  	if testing.Short() {
   516  		t.SkipNow()
   517  	}
   518  	tpt, err := createTpoolTester(t.Name())
   519  	if err != nil {
   520  		t.Fatal(err)
   521  	}
   522  	defer tpt.Close()
   523  
   524  	// Create and fund a valid file contract.
   525  	builder, err := tpt.wallet.StartTransaction()
   526  	if err != nil {
   527  		t.Fatal(err)
   528  	}
   529  	payout := types.NewCurrency64(1e9)
   530  	err = builder.FundSiacoins(payout)
   531  	if err != nil {
   532  		t.Fatal(err)
   533  	}
   534  	builder.AddFileContract(types.FileContract{
   535  		WindowStart:        tpt.cs.Height() + 2,
   536  		WindowEnd:          tpt.cs.Height() + 5,
   537  		Payout:             payout,
   538  		ValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   539  		MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   540  		UnlockHash:         types.UnlockConditions{}.UnlockHash(),
   541  	})
   542  	tSet, err := builder.Sign(true)
   543  	if err != nil {
   544  		t.Fatal(err)
   545  	}
   546  	err = tpt.tpool.AcceptTransactionSet(tSet)
   547  	if err != nil {
   548  		t.Fatal(err)
   549  	}
   550  	fcid := tSet[len(tSet)-1].FileContractID(0)
   551  
   552  	// Create a file contract revision and submit it.
   553  	rSet := []types.Transaction{{
   554  		FileContractRevisions: []types.FileContractRevision{{
   555  			ParentID:          fcid,
   556  			NewRevisionNumber: 2,
   557  
   558  			NewWindowStart:        tpt.cs.Height() + 2,
   559  			NewWindowEnd:          tpt.cs.Height() + 5,
   560  			NewValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   561  			NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   562  		}},
   563  	}}
   564  	err = tpt.tpool.AcceptTransactionSet(rSet)
   565  	if err != nil {
   566  		t.Fatal(err)
   567  	}
   568  }
   569  
   570  // TestPartialConfirmation checks that the transaction pool correctly accepts a
   571  // transaction set which has parents that have been accepted by the consensus
   572  // set but not the whole set has been accepted by the consensus set.
   573  func TestPartialConfirmation(t *testing.T) {
   574  	if testing.Short() {
   575  		t.SkipNow()
   576  	}
   577  	tpt, err := createTpoolTester(t.Name())
   578  	if err != nil {
   579  		t.Fatal(err)
   580  	}
   581  	defer tpt.Close()
   582  
   583  	// Create and fund a valid file contract.
   584  	builder, err := tpt.wallet.StartTransaction()
   585  	if err != nil {
   586  		t.Fatal(err)
   587  	}
   588  	payout := types.NewCurrency64(1e9)
   589  	err = builder.FundSiacoins(payout)
   590  	if err != nil {
   591  		t.Fatal(err)
   592  	}
   593  	builder.AddFileContract(types.FileContract{
   594  		WindowStart:        tpt.cs.Height() + 2,
   595  		WindowEnd:          tpt.cs.Height() + 5,
   596  		Payout:             payout,
   597  		ValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   598  		MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   599  		UnlockHash:         types.UnlockConditions{}.UnlockHash(),
   600  	})
   601  	tSet, err := builder.Sign(true)
   602  	if err != nil {
   603  		t.Fatal(err)
   604  	}
   605  	fcid := tSet[len(tSet)-1].FileContractID(0)
   606  
   607  	// Create a file contract revision.
   608  	rSet := []types.Transaction{{
   609  		FileContractRevisions: []types.FileContractRevision{{
   610  			ParentID:          fcid,
   611  			NewRevisionNumber: 2,
   612  
   613  			NewWindowStart:        tpt.cs.Height() + 2,
   614  			NewWindowEnd:          tpt.cs.Height() + 5,
   615  			NewValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   616  			NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
   617  		}},
   618  	}}
   619  
   620  	// Combine the contract and revision in to a single set.
   621  	fullSet := append(tSet, rSet...)
   622  
   623  	// Get the tSet onto the blockchain.
   624  	unsolvedBlock, target, err := tpt.miner.BlockForWork()
   625  	if err != nil {
   626  		t.Fatal(err)
   627  	}
   628  	unsolvedBlock.Transactions = append(unsolvedBlock.Transactions, tSet...)
   629  	solvedBlock, solved := tpt.miner.SolveBlock(unsolvedBlock, target)
   630  	if !solved {
   631  		t.Fatal("Failed to solve block")
   632  	}
   633  	err = tpt.cs.AcceptBlock(solvedBlock)
   634  	if err != nil {
   635  		t.Fatal(err)
   636  	}
   637  
   638  	// Try to get the full set into the transaction pool. The transaction pool
   639  	// should recognize that the set is partially accepted, and be able to
   640  	// accept on the the transactions that are new and are not yet on the
   641  	// blockchain.
   642  	err = tpt.tpool.AcceptTransactionSet(fullSet)
   643  	if err != nil {
   644  		t.Fatal(err)
   645  	}
   646  }
   647  
   648  // TestPartialConfirmationWeave checks that the transaction pool correctly
   649  // accepts a transaction set which has parents that have been accepted by the
   650  // consensus set but not the whole set has been accepted by the consensus set,
   651  // this time weaving the dependencies, such that the first transaction is not
   652  // in the consensus set, the second is, and the third has both as dependencies.
   653  func TestPartialConfirmationWeave(t *testing.T) {
   654  	if testing.Short() {
   655  		t.SkipNow()
   656  	}
   657  	tpt, err := createTpoolTester(t.Name())
   658  	if err != nil {
   659  		t.Fatal(err)
   660  	}
   661  	defer tpt.Close()
   662  
   663  	// Create a transaction with a single output to a fully controlled address.
   664  	emptyUH := types.UnlockConditions{}.UnlockHash()
   665  	builder1, err := tpt.wallet.StartTransaction()
   666  	if err != nil {
   667  		t.Fatal(err)
   668  	}
   669  	funding1 := types.NewCurrency64(1e9)
   670  	err = builder1.FundSiacoins(funding1)
   671  	if err != nil {
   672  		t.Fatal(err)
   673  	}
   674  	scOutput1 := types.SiacoinOutput{
   675  		Value:      funding1,
   676  		UnlockHash: emptyUH,
   677  	}
   678  	i1 := builder1.AddSiacoinOutput(scOutput1)
   679  	tSet1, err := builder1.Sign(true)
   680  	if err != nil {
   681  		t.Fatal(err)
   682  	}
   683  	// Submit to the transaction pool and mine the block, to minimize
   684  	// complexity.
   685  	err = tpt.tpool.AcceptTransactionSet(tSet1)
   686  	if err != nil {
   687  		t.Fatal(err)
   688  	}
   689  	_, err = tpt.miner.AddBlock()
   690  	if err != nil {
   691  		t.Fatal(err)
   692  	}
   693  
   694  	// Create a second output to the fully controlled address, to fund the
   695  	// second transaction in the weave.
   696  	builder2, err := tpt.wallet.StartTransaction()
   697  	if err != nil {
   698  		t.Fatal(err)
   699  	}
   700  	funding2 := types.NewCurrency64(2e9)
   701  	err = builder2.FundSiacoins(funding2)
   702  	if err != nil {
   703  		t.Fatal(err)
   704  	}
   705  	scOutput2 := types.SiacoinOutput{
   706  		Value:      funding2,
   707  		UnlockHash: emptyUH,
   708  	}
   709  	i2 := builder2.AddSiacoinOutput(scOutput2)
   710  	tSet2, err := builder2.Sign(true)
   711  	if err != nil {
   712  		t.Fatal(err)
   713  	}
   714  	// Submit to the transaction pool and mine the block, to minimize
   715  	// complexity.
   716  	err = tpt.tpool.AcceptTransactionSet(tSet2)
   717  	if err != nil {
   718  		t.Fatal(err)
   719  	}
   720  	_, err = tpt.miner.AddBlock()
   721  	if err != nil {
   722  		t.Fatal(err)
   723  	}
   724  
   725  	// Create a passthrough transaction for output1 and output2, so that they
   726  	// can be used as unconfirmed dependencies.
   727  	txn1 := types.Transaction{
   728  		SiacoinInputs: []types.SiacoinInput{{
   729  			ParentID: tSet1[len(tSet1)-1].SiacoinOutputID(i1),
   730  		}},
   731  		SiacoinOutputs: []types.SiacoinOutput{{
   732  			Value:      funding1,
   733  			UnlockHash: emptyUH,
   734  		}},
   735  	}
   736  	txn2 := types.Transaction{
   737  		SiacoinInputs: []types.SiacoinInput{{
   738  			ParentID: tSet2[len(tSet2)-1].SiacoinOutputID(i2),
   739  		}},
   740  		SiacoinOutputs: []types.SiacoinOutput{{
   741  			Value:      funding2,
   742  			UnlockHash: emptyUH,
   743  		}},
   744  	}
   745  
   746  	// Create a child transaction that depends on inputs from both txn1 and
   747  	// txn2.
   748  	child := types.Transaction{
   749  		SiacoinInputs: []types.SiacoinInput{
   750  			{
   751  				ParentID: txn1.SiacoinOutputID(0),
   752  			},
   753  			{
   754  				ParentID: txn2.SiacoinOutputID(0),
   755  			},
   756  		},
   757  		SiacoinOutputs: []types.SiacoinOutput{{
   758  			Value: funding1.Add(funding2),
   759  		}},
   760  	}
   761  
   762  	// Get txn2 accepted into the consensus set.
   763  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn2})
   764  	if err != nil {
   765  		t.Fatal(err)
   766  	}
   767  	_, err = tpt.miner.AddBlock()
   768  	if err != nil {
   769  		t.Fatal(err)
   770  	}
   771  
   772  	// Try to get the set of txn1, txn2, and child accepted into the
   773  	// transaction pool.
   774  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn1, txn2, child})
   775  	if err != nil {
   776  		t.Fatal(err)
   777  	}
   778  }