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

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