gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/consensus/validtransaction_test.go (about)

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