github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/validtransaction_test.go (about)

     1  package consensus
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/NebulousLabs/Sia/build"
     7  	"github.com/NebulousLabs/Sia/crypto"
     8  	"github.com/NebulousLabs/Sia/types"
     9  	"github.com/NebulousLabs/fastrand"
    10  
    11  	"github.com/coreos/bbolt"
    12  )
    13  
    14  // TestTryValidTransactionSet submits a valid transaction set to the
    15  // TryTransactionSet method.
    16  func TestTryValidTransactionSet(t *testing.T) {
    17  	if testing.Short() {
    18  		t.SkipNow()
    19  	}
    20  	t.Parallel()
    21  	cst, err := createConsensusSetTester(t.Name())
    22  	if err != nil {
    23  		t.Fatal(err)
    24  	}
    25  	defer cst.Close()
    26  	initialHash := cst.cs.dbConsensusChecksum()
    27  
    28  	// Try a valid transaction.
    29  	_, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{})
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	txns := cst.tpool.TransactionList()
    34  	cc, err := cst.cs.TryTransactionSet(txns)
    35  	if err != nil {
    36  		t.Error(err)
    37  	}
    38  	if cst.cs.dbConsensusChecksum() != initialHash {
    39  		t.Error("TryTransactionSet did not resotre order")
    40  	}
    41  	if len(cc.SiacoinOutputDiffs) == 0 {
    42  		t.Error("consensus change is missing diffs after verifying a transction clump")
    43  	}
    44  }
    45  
    46  // TestTryInvalidTransactionSet submits an invalid transaction set to the
    47  // TryTransaction method.
    48  func TestTryInvalidTransactionSet(t *testing.T) {
    49  	if testing.Short() {
    50  		t.SkipNow()
    51  	}
    52  	t.Parallel()
    53  	cst, err := createConsensusSetTester(t.Name())
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  	defer cst.Close()
    58  	initialHash := cst.cs.dbConsensusChecksum()
    59  
    60  	// Try a valid transaction followed by an invalid transaction.
    61  	_, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{})
    62  	if err != nil {
    63  		t.Fatal(err)
    64  	}
    65  	txns := cst.tpool.TransactionList()
    66  	txn := types.Transaction{
    67  		SiacoinInputs: []types.SiacoinInput{{}},
    68  	}
    69  	txns = append(txns, txn)
    70  	cc, err := cst.cs.TryTransactionSet(txns)
    71  	if err == nil {
    72  		t.Error("bad transaction survived filter")
    73  	}
    74  	if cst.cs.dbConsensusChecksum() != initialHash {
    75  		t.Error("TryTransactionSet did not restore order")
    76  	}
    77  	if len(cc.SiacoinOutputDiffs) != 0 {
    78  		t.Error("consensus change was not empty despite an error being returned")
    79  	}
    80  }
    81  
    82  // TestStorageProofBoundaries creates file contracts and submits storage proofs
    83  // for them, probing segment boundaries (first segment, last segment,
    84  // incomplete segment, etc.).
    85  func TestStorageProofBoundaries(t *testing.T) {
    86  	if testing.Short() || !build.VLONG {
    87  		t.SkipNow()
    88  	}
    89  	t.Parallel()
    90  	cst, err := createConsensusSetTester(t.Name())
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	defer cst.Close()
    95  
    96  	// Mine enough blocks to put us beyond the testing hardfork.
    97  	for i := 0; i < 10; i++ {
    98  		_, err = cst.miner.AddBlock()
    99  		if err != nil {
   100  			t.Fatal(err)
   101  		}
   102  	}
   103  
   104  	// Try storage proofs on data between 0 bytes and 128 bytes (0 segments and
   105  	// 1 segment). Perform the operation five times because we can't control
   106  	// which segment gets selected - it is randomly decided by the block.
   107  	segmentRange := []int{0, 1, 2, 3, 4, 5, 15, 25, 30, 32, 62, 63, 64, 65, 66, 70, 81, 89, 90, 126, 127, 128, 129}
   108  	for i := 0; i < 3; i++ {
   109  		randData := fastrand.Bytes(140)
   110  
   111  		// Create a file contract for all sizes of the data between 0 and 2
   112  		// segments and put them in the transaction pool.
   113  		var fcids []types.FileContractID
   114  		for _, k := range segmentRange {
   115  			// Create the data and the file contract around it.
   116  			truncatedData := randData[:k]
   117  			fc := types.FileContract{
   118  				FileSize:           uint64(k),
   119  				FileMerkleRoot:     crypto.MerkleRoot(truncatedData),
   120  				WindowStart:        cst.cs.dbBlockHeight() + 2,
   121  				WindowEnd:          cst.cs.dbBlockHeight() + 4,
   122  				Payout:             types.NewCurrency64(500), // Too small to be subject to siafund fee.
   123  				ValidProofOutputs:  []types.SiacoinOutput{{Value: types.NewCurrency64(500)}},
   124  				MissedProofOutputs: []types.SiacoinOutput{{Value: types.NewCurrency64(500)}},
   125  			}
   126  
   127  			// Create a transaction around the file contract and add it to the
   128  			// transaction pool.
   129  			b, err := cst.wallet.StartTransaction()
   130  			if err != nil {
   131  				t.Fatal(err)
   132  			}
   133  			err = b.FundSiacoins(types.NewCurrency64(500))
   134  			if err != nil {
   135  				t.Fatal(err)
   136  			}
   137  			b.AddFileContract(fc)
   138  			txnSet, err := b.Sign(true)
   139  			if err != nil {
   140  				t.Fatal(err)
   141  			}
   142  			err = cst.tpool.AcceptTransactionSet(txnSet)
   143  			if err != nil {
   144  				t.Fatal(err)
   145  			}
   146  
   147  			// Store the file contract id for later when building the storage
   148  			// proof.
   149  			fcids = append(fcids, txnSet[len(txnSet)-1].FileContractID(0))
   150  		}
   151  
   152  		// Mine blocks to get the file contracts into the blockchain and
   153  		// confirming.
   154  		for j := 0; j < 2; j++ {
   155  			_, err = cst.miner.AddBlock()
   156  			if err != nil {
   157  				t.Fatal(err)
   158  			}
   159  		}
   160  
   161  		// Create storage proofs for the file contracts and submit the proofs
   162  		// to the blockchain.
   163  		for j, k := range segmentRange {
   164  			// Build the storage proof.
   165  			truncatedData := randData[:k]
   166  			proofIndex, err := cst.cs.StorageProofSegment(fcids[j])
   167  			if err != nil {
   168  				t.Fatal(err)
   169  			}
   170  			base, hashSet := crypto.MerkleProof(truncatedData, proofIndex)
   171  			sp := types.StorageProof{
   172  				ParentID: fcids[j],
   173  				HashSet:  hashSet,
   174  			}
   175  			copy(sp.Segment[:], base)
   176  
   177  			if k > 0 {
   178  				// Try submitting an empty storage proof, to make sure that the
   179  				// hardfork code didn't accidentally allow empty storage proofs
   180  				// in situations other than file sizes with 0 bytes.
   181  				badSP := types.StorageProof{ParentID: fcids[j]}
   182  				badTxn := types.Transaction{
   183  					StorageProofs: []types.StorageProof{badSP},
   184  				}
   185  				if sp.Segment == badSP.Segment {
   186  					continue
   187  				}
   188  				err = cst.tpool.AcceptTransactionSet([]types.Transaction{badTxn})
   189  				if err == nil {
   190  					t.Fatal("An empty storage proof got into the transaction pool with non-empty data")
   191  				}
   192  			}
   193  
   194  			// Submit the storage proof to the blockchain in a transaction.
   195  			txn := types.Transaction{
   196  				StorageProofs: []types.StorageProof{sp},
   197  			}
   198  			err = cst.tpool.AcceptTransactionSet([]types.Transaction{txn})
   199  			if err != nil {
   200  				t.Fatal(err, "-", j, k)
   201  			}
   202  		}
   203  
   204  		// Mine blocks to get the storage proofs on the blockchain.
   205  		for j := 0; j < 2; j++ {
   206  			_, err = cst.miner.AddBlock()
   207  			if err != nil {
   208  				t.Fatal(err)
   209  			}
   210  		}
   211  	}
   212  }
   213  
   214  // TestEmptyStorageProof creates file contracts and submits storage proofs for
   215  // them, probing segment boundaries (first segment, last segment, incomplete
   216  // segment, etc.).
   217  func TestEmptyStorageProof(t *testing.T) {
   218  	if testing.Short() || !build.VLONG {
   219  		t.SkipNow()
   220  	}
   221  	t.Parallel()
   222  	cst, err := createConsensusSetTester(t.Name())
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	defer cst.Close()
   227  
   228  	// Mine enough blocks to put us beyond the testing hardfork.
   229  	for i := 0; i < 10; i++ {
   230  		_, err = cst.miner.AddBlock()
   231  		if err != nil {
   232  			t.Fatal(err)
   233  		}
   234  	}
   235  
   236  	// Try storage proofs on data between 0 bytes and 128 bytes (0 segments and
   237  	// 1 segment). Perform the operation five times because we can't control
   238  	// which segment gets selected - it is randomly decided by the block.
   239  	segmentRange := []int{0, 1, 2, 3, 4, 5, 15, 25, 30, 32, 62, 63, 64, 65, 66, 70, 81, 89, 90, 126, 127, 128, 129}
   240  	for i := 0; i < 3; i++ {
   241  		randData := fastrand.Bytes(140)
   242  
   243  		// Create a file contract for all sizes of the data between 0 and 2
   244  		// segments and put them in the transaction pool.
   245  		var fcids []types.FileContractID
   246  		for _, k := range segmentRange {
   247  			// Create the data and the file contract around it.
   248  			truncatedData := randData[:k]
   249  			fc := types.FileContract{
   250  				FileSize:           uint64(k),
   251  				FileMerkleRoot:     crypto.MerkleRoot(truncatedData),
   252  				WindowStart:        cst.cs.dbBlockHeight() + 2,
   253  				WindowEnd:          cst.cs.dbBlockHeight() + 4,
   254  				Payout:             types.NewCurrency64(500), // Too small to be subject to siafund fee.
   255  				ValidProofOutputs:  []types.SiacoinOutput{{Value: types.NewCurrency64(500)}},
   256  				MissedProofOutputs: []types.SiacoinOutput{{Value: types.NewCurrency64(500)}},
   257  			}
   258  
   259  			// Create a transaction around the file contract and add it to the
   260  			// transaction pool.
   261  			b, err := cst.wallet.StartTransaction()
   262  			if err != nil {
   263  				t.Fatal(err)
   264  			}
   265  			err = b.FundSiacoins(types.NewCurrency64(500))
   266  			if err != nil {
   267  				t.Fatal(err)
   268  			}
   269  			b.AddFileContract(fc)
   270  			txnSet, err := b.Sign(true)
   271  			if err != nil {
   272  				t.Fatal(err)
   273  			}
   274  			err = cst.tpool.AcceptTransactionSet(txnSet)
   275  			if err != nil {
   276  				t.Fatal(err)
   277  			}
   278  
   279  			// Store the file contract id for later when building the storage
   280  			// proof.
   281  			fcids = append(fcids, txnSet[len(txnSet)-1].FileContractID(0))
   282  		}
   283  
   284  		// Mine blocks to get the file contracts into the blockchain and
   285  		// confirming.
   286  		for j := 0; j < 2; j++ {
   287  			_, err = cst.miner.AddBlock()
   288  			if err != nil {
   289  				t.Fatal(err)
   290  			}
   291  		}
   292  
   293  		// Create storage proofs for the file contracts and submit the proofs
   294  		// to the blockchain.
   295  		for j, k := range segmentRange {
   296  			// Build the storage proof.
   297  			truncatedData := randData[:k]
   298  			proofIndex, err := cst.cs.StorageProofSegment(fcids[j])
   299  			if err != nil {
   300  				t.Fatal(err)
   301  			}
   302  			base, hashSet := crypto.MerkleProof(truncatedData, proofIndex)
   303  			sp := types.StorageProof{
   304  				ParentID: fcids[j],
   305  				HashSet:  hashSet,
   306  			}
   307  			copy(sp.Segment[:], base)
   308  
   309  			// Submit the storage proof to the blockchain in a transaction.
   310  			txn := types.Transaction{
   311  				StorageProofs: []types.StorageProof{sp},
   312  			}
   313  			err = cst.tpool.AcceptTransactionSet([]types.Transaction{txn})
   314  			if err != nil {
   315  				t.Fatal(err, "-", j, k)
   316  			}
   317  		}
   318  
   319  		// Mine blocks to get the storage proofs on the blockchain.
   320  		for j := 0; j < 2; j++ {
   321  			_, err = cst.miner.AddBlock()
   322  			if err != nil {
   323  				t.Fatal(err)
   324  			}
   325  		}
   326  	}
   327  }
   328  
   329  // TestValidSiacoins probes the validSiacoins method of the consensus set.
   330  func TestValidSiacoins(t *testing.T) {
   331  	if testing.Short() {
   332  		t.SkipNow()
   333  	}
   334  	t.Parallel()
   335  	cst, err := createConsensusSetTester(t.Name())
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  	defer cst.Close()
   340  
   341  	// Create a transaction pointing to a nonexistent siacoin output.
   342  	txn := types.Transaction{
   343  		SiacoinInputs: []types.SiacoinInput{{}},
   344  	}
   345  	err = cst.cs.db.View(func(tx *bolt.Tx) error {
   346  		err := validSiacoins(tx, txn)
   347  		if err != errMissingSiacoinOutput {
   348  			t.Fatal(err)
   349  		}
   350  		return nil
   351  	})
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  
   356  	// Create a transaction with invalid unlock conditions.
   357  	scoid, _, err := cst.cs.getArbSiacoinOutput()
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  	txn = types.Transaction{
   362  		SiacoinInputs: []types.SiacoinInput{{
   363  			ParentID: scoid,
   364  		}},
   365  	}
   366  	err = cst.cs.db.View(func(tx *bolt.Tx) error {
   367  		err := validSiacoins(tx, txn)
   368  		if err != errWrongUnlockConditions {
   369  			t.Fatal(err)
   370  		}
   371  		return nil
   372  	})
   373  	if err != nil {
   374  		t.Fatal(err)
   375  	}
   376  
   377  	// Create a txn with more outputs than inputs.
   378  	txn = types.Transaction{
   379  		SiacoinOutputs: []types.SiacoinOutput{{
   380  			Value: types.NewCurrency64(1),
   381  		}},
   382  	}
   383  	err = cst.cs.db.View(func(tx *bolt.Tx) error {
   384  		err := validSiacoins(tx, txn)
   385  		if err != errSiacoinInputOutputMismatch {
   386  			t.Fatal(err)
   387  		}
   388  		return nil
   389  	})
   390  	if err != nil {
   391  		t.Fatal(err)
   392  	}
   393  }
   394  
   395  // TestStorageProofSegment probes the storageProofSegment method of the
   396  // consensus set.
   397  func TestStorageProofSegment(t *testing.T) {
   398  	if testing.Short() {
   399  		t.SkipNow()
   400  	}
   401  	t.Parallel()
   402  	cst, err := createConsensusSetTester(t.Name())
   403  	if err != nil {
   404  		t.Fatal(err)
   405  	}
   406  	defer cst.Close()
   407  
   408  	// Submit a file contract that is unrecognized.
   409  	_, err = cst.cs.dbStorageProofSegment(types.FileContractID{})
   410  	if err != errUnrecognizedFileContractID {
   411  		t.Error(err)
   412  	}
   413  
   414  	// Try to get the segment of an unfinished file contract.
   415  	cst.cs.dbAddFileContract(types.FileContractID{}, types.FileContract{
   416  		Payout:      types.NewCurrency64(1),
   417  		WindowStart: 100000,
   418  	})
   419  	_, err = cst.cs.dbStorageProofSegment(types.FileContractID{})
   420  	if err != errUnfinishedFileContract {
   421  		t.Error(err)
   422  	}
   423  }
   424  
   425  // TestValidStorageProofs probes the validStorageProofs method of the consensus
   426  // set.
   427  func TestValidStorageProofs(t *testing.T) {
   428  	if testing.Short() {
   429  		t.SkipNow()
   430  	}
   431  	t.Parallel()
   432  	cst, err := createConsensusSetTester(t.Name())
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  	defer cst.Close()
   437  
   438  	// COMPATv0.4.0
   439  	//
   440  	// Mine 10 blocks so that the post-hardfork rules are in effect.
   441  	for i := 0; i < 10; i++ {
   442  		block, _ := cst.miner.FindBlock()
   443  		err = cst.cs.AcceptBlock(block)
   444  		if err != nil {
   445  			t.Fatal(err)
   446  		}
   447  	}
   448  
   449  	// Create a file contract for which a storage proof can be created.
   450  	var fcid types.FileContractID
   451  	fcid[0] = 12
   452  	simFile := fastrand.Bytes(64 * 1024)
   453  	root := crypto.MerkleRoot(simFile)
   454  	fc := types.FileContract{
   455  		FileSize:       64 * 1024,
   456  		FileMerkleRoot: root,
   457  		Payout:         types.NewCurrency64(1),
   458  		WindowStart:    2,
   459  		WindowEnd:      1200,
   460  	}
   461  	cst.cs.dbAddFileContract(fcid, fc)
   462  
   463  	// Create a transaction with a storage proof.
   464  	proofIndex, err := cst.cs.dbStorageProofSegment(fcid)
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	base, proofSet := crypto.MerkleProof(simFile, proofIndex)
   469  	txn := types.Transaction{
   470  		StorageProofs: []types.StorageProof{{
   471  			ParentID: fcid,
   472  			HashSet:  proofSet,
   473  		}},
   474  	}
   475  	copy(txn.StorageProofs[0].Segment[:], base)
   476  	err = cst.cs.dbValidStorageProofs(txn)
   477  	if err != nil {
   478  		t.Error(err)
   479  	}
   480  
   481  	// Corrupt the proof set.
   482  	proofSet[0][0]++
   483  	txn = types.Transaction{
   484  		StorageProofs: []types.StorageProof{{
   485  			ParentID: fcid,
   486  			HashSet:  proofSet,
   487  		}},
   488  	}
   489  	copy(txn.StorageProofs[0].Segment[:], base)
   490  	err = cst.cs.dbValidStorageProofs(txn)
   491  	if err != errInvalidStorageProof {
   492  		t.Error(err)
   493  	}
   494  
   495  	// Try to validate a proof for a file contract that doesn't exist.
   496  	txn.StorageProofs[0].ParentID = types.FileContractID{}
   497  	err = cst.cs.dbValidStorageProofs(txn)
   498  	if err != errUnrecognizedFileContractID {
   499  		t.Error(err)
   500  	}
   501  
   502  	// Try a proof set where there is padding on the last segment in the file.
   503  	file := fastrand.Bytes(100)
   504  	root = crypto.MerkleRoot(file)
   505  	fc = types.FileContract{
   506  		FileSize:       100,
   507  		FileMerkleRoot: root,
   508  		Payout:         types.NewCurrency64(1),
   509  		WindowStart:    2,
   510  		WindowEnd:      1200,
   511  	}
   512  
   513  	// Find a proofIndex that has the value '1'.
   514  	for {
   515  		fcid[0]++
   516  		cst.cs.dbAddFileContract(fcid, fc)
   517  		proofIndex, err = cst.cs.dbStorageProofSegment(fcid)
   518  		if err != nil {
   519  			t.Fatal(err)
   520  		}
   521  		if proofIndex == 1 {
   522  			break
   523  		}
   524  	}
   525  	base, proofSet = crypto.MerkleProof(file, proofIndex)
   526  	txn = types.Transaction{
   527  		StorageProofs: []types.StorageProof{{
   528  			ParentID: fcid,
   529  			HashSet:  proofSet,
   530  		}},
   531  	}
   532  	copy(txn.StorageProofs[0].Segment[:], base)
   533  	err = cst.cs.dbValidStorageProofs(txn)
   534  	if err != nil {
   535  		t.Fatal(err)
   536  	}
   537  }
   538  
   539  // HARDFORK 21,000
   540  //
   541  // TestPreForkValidStorageProofs checks that storage proofs which are invalid
   542  // before the hardfork (but valid afterwards) are still rejected before the
   543  // hardfork).
   544  func TestPreForkValidStorageProofs(t *testing.T) {
   545  	if testing.Short() {
   546  		t.SkipNow()
   547  	}
   548  	t.Parallel()
   549  	cst, err := createConsensusSetTester(t.Name())
   550  	if err != nil {
   551  		t.Fatal(err)
   552  	}
   553  	defer cst.Close()
   554  
   555  	// Try a proof set where there is padding on the last segment in the file.
   556  	file := fastrand.Bytes(100)
   557  	root := crypto.MerkleRoot(file)
   558  	fc := types.FileContract{
   559  		FileSize:       100,
   560  		FileMerkleRoot: root,
   561  		Payout:         types.NewCurrency64(1),
   562  		WindowStart:    2,
   563  		WindowEnd:      1200,
   564  	}
   565  
   566  	// Find a proofIndex that has the value '1'.
   567  	var fcid types.FileContractID
   568  	var proofIndex uint64
   569  	for {
   570  		fcid[0]++
   571  		cst.cs.dbAddFileContract(fcid, fc)
   572  		proofIndex, err = cst.cs.dbStorageProofSegment(fcid)
   573  		if err != nil {
   574  			t.Fatal(err)
   575  		}
   576  		if proofIndex == 1 {
   577  			break
   578  		}
   579  	}
   580  	base, proofSet := crypto.MerkleProof(file, proofIndex)
   581  	txn := types.Transaction{
   582  		StorageProofs: []types.StorageProof{{
   583  			ParentID: fcid,
   584  			HashSet:  proofSet,
   585  		}},
   586  	}
   587  	copy(txn.StorageProofs[0].Segment[:], base)
   588  	err = cst.cs.dbValidStorageProofs(txn)
   589  	if err != errInvalidStorageProof {
   590  		t.Log(cst.cs.dbBlockHeight())
   591  		t.Fatal(err)
   592  	}
   593  }
   594  
   595  // TestValidFileContractRevisions probes the validFileContractRevisions method
   596  // of the consensus set.
   597  func TestValidFileContractRevisions(t *testing.T) {
   598  	if testing.Short() {
   599  		t.SkipNow()
   600  	}
   601  	t.Parallel()
   602  	cst, err := createConsensusSetTester(t.Name())
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  	defer cst.Close()
   607  
   608  	// Grab an address + unlock conditions for the transaction.
   609  	unlockConditions, err := cst.wallet.NextAddress()
   610  	if err != nil {
   611  		t.Fatal(err)
   612  	}
   613  
   614  	// Create a file contract for which a storage proof can be created.
   615  	var fcid types.FileContractID
   616  	fcid[0] = 12
   617  	simFile := fastrand.Bytes(64 * 1024)
   618  	root := crypto.MerkleRoot(simFile)
   619  	fc := types.FileContract{
   620  		FileSize:       64 * 1024,
   621  		FileMerkleRoot: root,
   622  		WindowStart:    102,
   623  		WindowEnd:      1200,
   624  		Payout:         types.NewCurrency64(1),
   625  		UnlockHash:     unlockConditions.UnlockHash(),
   626  		RevisionNumber: 1,
   627  	}
   628  	cst.cs.dbAddFileContract(fcid, fc)
   629  
   630  	// Try a working file contract revision.
   631  	txn := types.Transaction{
   632  		FileContractRevisions: []types.FileContractRevision{
   633  			{
   634  				ParentID:          fcid,
   635  				UnlockConditions:  unlockConditions,
   636  				NewRevisionNumber: 2,
   637  			},
   638  		},
   639  	}
   640  	err = cst.cs.dbValidFileContractRevisions(txn)
   641  	if err != nil {
   642  		t.Error(err)
   643  	}
   644  
   645  	// Try a transaction with an insufficient revision number.
   646  	txn = types.Transaction{
   647  		FileContractRevisions: []types.FileContractRevision{
   648  			{
   649  				ParentID:          fcid,
   650  				UnlockConditions:  unlockConditions,
   651  				NewRevisionNumber: 1,
   652  			},
   653  		},
   654  	}
   655  	err = cst.cs.dbValidFileContractRevisions(txn)
   656  	if err != errLowRevisionNumber {
   657  		t.Error(err)
   658  	}
   659  	txn = types.Transaction{
   660  		FileContractRevisions: []types.FileContractRevision{
   661  			{
   662  				ParentID:          fcid,
   663  				UnlockConditions:  unlockConditions,
   664  				NewRevisionNumber: 0,
   665  			},
   666  		},
   667  	}
   668  	err = cst.cs.dbValidFileContractRevisions(txn)
   669  	if err != errLowRevisionNumber {
   670  		t.Error(err)
   671  	}
   672  
   673  	// Submit a file contract revision pointing to an invalid parent.
   674  	txn.FileContractRevisions[0].ParentID[0]--
   675  	err = cst.cs.dbValidFileContractRevisions(txn)
   676  	if err != errNilItem {
   677  		t.Error(err)
   678  	}
   679  	txn.FileContractRevisions[0].ParentID[0]++
   680  
   681  	// Submit a file contract revision for a file contract whose window has
   682  	// already opened.
   683  	fc, err = cst.cs.dbGetFileContract(fcid)
   684  	if err != nil {
   685  		t.Fatal(err)
   686  	}
   687  	fc.WindowStart = 0
   688  	cst.cs.dbRemoveFileContract(fcid)
   689  	cst.cs.dbAddFileContract(fcid, fc)
   690  	txn.FileContractRevisions[0].NewRevisionNumber = 3
   691  	err = cst.cs.dbValidFileContractRevisions(txn)
   692  	if err != errLateRevision {
   693  		t.Error(err)
   694  	}
   695  
   696  	// Submit a file contract revision with incorrect unlock conditions.
   697  	fc.WindowStart = 100
   698  	cst.cs.dbRemoveFileContract(fcid)
   699  	cst.cs.dbAddFileContract(fcid, fc)
   700  	txn.FileContractRevisions[0].UnlockConditions.Timelock++
   701  	err = cst.cs.dbValidFileContractRevisions(txn)
   702  	if err != errWrongUnlockConditions {
   703  		t.Error(err)
   704  	}
   705  	txn.FileContractRevisions[0].UnlockConditions.Timelock--
   706  
   707  	// Submit file contract revisions for file contracts with altered payouts.
   708  	txn.FileContractRevisions[0].NewValidProofOutputs = []types.SiacoinOutput{{
   709  		Value: types.NewCurrency64(1),
   710  	}}
   711  	txn.FileContractRevisions[0].NewMissedProofOutputs = []types.SiacoinOutput{{
   712  		Value: types.NewCurrency64(1),
   713  	}}
   714  	err = cst.cs.dbValidFileContractRevisions(txn)
   715  	if err != errAlteredRevisionPayouts {
   716  		t.Error(err)
   717  	}
   718  	txn.FileContractRevisions[0].NewValidProofOutputs = nil
   719  	err = cst.cs.dbValidFileContractRevisions(txn)
   720  	if err != errAlteredRevisionPayouts {
   721  		t.Error(err)
   722  	}
   723  	txn.FileContractRevisions[0].NewValidProofOutputs = []types.SiacoinOutput{{
   724  		Value: types.NewCurrency64(1),
   725  	}}
   726  	txn.FileContractRevisions[0].NewMissedProofOutputs = nil
   727  	err = cst.cs.dbValidFileContractRevisions(txn)
   728  	if err != errAlteredRevisionPayouts {
   729  		t.Error(err)
   730  	}
   731  }
   732  
   733  /*
   734  // TestValidSiafunds probes the validSiafunds mthod of the consensus set.
   735  func TestValidSiafunds(t *testing.T) {
   736  	if testing.Short() {
   737  		t.SkipNow()
   738  	}
   739  	cst, err := createConsensusSetTester(t.Name())
   740  	if err != nil {
   741  		t.Fatal(err)
   742  	}
   743  	defer cst.closeCst()
   744  
   745  	// Create a transaction pointing to a nonexistent siafund output.
   746  	txn := types.Transaction{
   747  		SiafundInputs: []types.SiafundInput{{}},
   748  	}
   749  	err = cst.cs.validSiafunds(txn)
   750  	if err != ErrMissingSiafundOutput {
   751  		t.Error(err)
   752  	}
   753  
   754  	// Create a transaction with invalid unlock conditions.
   755  	var sfoid types.SiafundOutputID
   756  	cst.cs.db.forEachSiafundOutputs(func(mapSfoid types.SiafundOutputID, sfo types.SiafundOutput) {
   757  		sfoid = mapSfoid
   758  		// pointless to do this but I can't think of a better way.
   759  	})
   760  	txn = types.Transaction{
   761  		SiafundInputs: []types.SiafundInput{{
   762  			ParentID:         sfoid,
   763  			UnlockConditions: types.UnlockConditions{Timelock: 12345}, // avoid collisions with existing outputs
   764  		}},
   765  	}
   766  	err = cst.cs.validSiafunds(txn)
   767  	if err != ErrWrongUnlockConditions {
   768  		t.Error(err)
   769  	}
   770  
   771  	// Create a transaction with more outputs than inputs.
   772  	txn = types.Transaction{
   773  		SiafundOutputs: []types.SiafundOutput{{
   774  			Value: types.NewCurrency64(1),
   775  		}},
   776  	}
   777  	err = cst.cs.validSiafunds(txn)
   778  	if err != ErrSiafundInputOutputMismatch {
   779  		t.Error(err)
   780  	}
   781  }
   782  
   783  // TestValidTransaction probes the validTransaction method of the consensus
   784  // set.
   785  func TestValidTransaction(t *testing.T) {
   786  	if testing.Short() {
   787  		t.SkipNow()
   788  	}
   789  	cst, err := createConsensusSetTester(t.Name())
   790  	if err != nil {
   791  		t.Fatal(err)
   792  	}
   793  	defer cst.closeCst()
   794  
   795  	// Create a transaction that is not standalone valid.
   796  	txn := types.Transaction{
   797  		FileContracts: []types.FileContract{{
   798  			WindowStart: 0,
   799  		}},
   800  	}
   801  	err = cst.cs.validTransaction(txn)
   802  	if err == nil {
   803  		t.Error("transaction is valid")
   804  	}
   805  
   806  	// Create a transaction with invalid siacoins.
   807  	txn = types.Transaction{
   808  		SiacoinInputs: []types.SiacoinInput{{}},
   809  	}
   810  	err = cst.cs.validTransaction(txn)
   811  	if err == nil {
   812  		t.Error("transaction is valid")
   813  	}
   814  
   815  	// Create a transaction with invalid storage proofs.
   816  	txn = types.Transaction{
   817  		StorageProofs: []types.StorageProof{{}},
   818  	}
   819  	err = cst.cs.validTransaction(txn)
   820  	if err == nil {
   821  		t.Error("transaction is valid")
   822  	}
   823  
   824  	// Create a transaction with invalid file contract revisions.
   825  	txn = types.Transaction{
   826  		FileContractRevisions: []types.FileContractRevision{{
   827  			NewWindowStart: 5000,
   828  			NewWindowEnd:   5005,
   829  			ParentID:       types.FileContractID{},
   830  		}},
   831  	}
   832  	err = cst.cs.validTransaction(txn)
   833  	if err == nil {
   834  		t.Error("transaction is valid")
   835  	}
   836  
   837  	// Create a transaction with invalid siafunds.
   838  	txn = types.Transaction{
   839  		SiafundInputs: []types.SiafundInput{{}},
   840  	}
   841  	err = cst.cs.validTransaction(txn)
   842  	if err == nil {
   843  		t.Error("transaction is valid")
   844  	}
   845  }
   846  */