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