github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/validtransaction_test.go (about)

     1  package consensus
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/NebulousLabs/Sia/types"
     7  )
     8  
     9  // TestTryValidTransactionSet submits a valid transaction set to the
    10  // TryTransactionSet method.
    11  func TestTryValidTransactionSet(t *testing.T) {
    12  	if testing.Short() {
    13  		t.SkipNow()
    14  	}
    15  	cst, err := createConsensusSetTester("TestValidTransaction")
    16  	if err != nil {
    17  		t.Fatal(err)
    18  	}
    19  	defer cst.Close()
    20  	initialHash := cst.cs.dbConsensusChecksum()
    21  
    22  	// Try a valid transaction.
    23  	_, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{})
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	txns := cst.tpool.TransactionList()
    28  	cc, err := cst.cs.TryTransactionSet(txns)
    29  	if err != nil {
    30  		t.Error(err)
    31  	}
    32  	if cst.cs.dbConsensusChecksum() != initialHash {
    33  		t.Error("TryTransactionSet did not resotre order")
    34  	}
    35  	if len(cc.SiacoinOutputDiffs) == 0 {
    36  		t.Error("consensus change is missing diffs after verifying a transction clump")
    37  	}
    38  }
    39  
    40  // TestTryInvalidTransactionSet submits an invalid transaction set to the
    41  // TryTransaction method.
    42  func TestTryInvalidTransactionSet(t *testing.T) {
    43  	if testing.Short() {
    44  		t.SkipNow()
    45  	}
    46  	cst, err := createConsensusSetTester("TestValidTransaction")
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  	defer cst.Close()
    51  	initialHash := cst.cs.dbConsensusChecksum()
    52  
    53  	// Try a valid transaction followed by an invalid transaction.
    54  	_, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{})
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	txns := cst.tpool.TransactionList()
    59  	txn := types.Transaction{
    60  		SiacoinInputs: []types.SiacoinInput{{}},
    61  	}
    62  	txns = append(txns, txn)
    63  	cc, err := cst.cs.TryTransactionSet(txns)
    64  	if err == nil {
    65  		t.Error("bad transaction survived filter")
    66  	}
    67  	if cst.cs.dbConsensusChecksum() != initialHash {
    68  		t.Error("TryTransactionSet did not restore order")
    69  	}
    70  	if len(cc.SiacoinOutputDiffs) != 0 {
    71  		t.Error("consensus change was not empty despite an error being returned")
    72  	}
    73  }
    74  
    75  /*
    76  // TestValidSiacoins probes the validSiacoins method of the consensus set.
    77  func TestValidSiacoins(t *testing.T) {
    78  	if testing.Short() {
    79  		t.SkipNow()
    80  	}
    81  	cst, err := createConsensusSetTester("TestValidSiacoins")
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	defer cst.closeCst()
    86  
    87  	// Create a transaction pointing to a nonexistent siacoin output.
    88  	txn := types.Transaction{
    89  		SiacoinInputs: []types.SiacoinInput{{}},
    90  	}
    91  	err = cst.cs.validSiacoins(txn)
    92  	if err != ErrMissingSiacoinOutput {
    93  		t.Error(err)
    94  	}
    95  
    96  	// Create a transaction with invalid unlock conditions.
    97  	var scoid types.SiacoinOutputID
    98  	cst.cs.db.forEachSiacoinOutputs(func(mapScoid types.SiacoinOutputID, sco types.SiacoinOutput) {
    99  		scoid = mapScoid
   100  	})
   101  	txn = types.Transaction{
   102  		SiacoinInputs: []types.SiacoinInput{{
   103  			ParentID: scoid,
   104  		}},
   105  	}
   106  	err = cst.cs.validSiacoins(txn)
   107  	if err != ErrWrongUnlockConditions {
   108  		t.Error(err)
   109  	}
   110  
   111  	// Create a txn with more outputs than inputs.
   112  	txn = types.Transaction{
   113  		SiacoinOutputs: []types.SiacoinOutput{{
   114  			Value: types.NewCurrency64(1),
   115  		}},
   116  	}
   117  	err = cst.cs.validSiacoins(txn)
   118  	if err != ErrSiacoinInputOutputMismatch {
   119  		t.Error(err)
   120  	}
   121  }
   122  
   123  // TestStorageProofSegment probes the storageProofSegment method of the
   124  // consensus set.
   125  func TestStorageProofSegment(t *testing.T) {
   126  	if testing.Short() {
   127  		t.SkipNow()
   128  	}
   129  	cst, err := createConsensusSetTester("TestStorageProofSegment")
   130  	if err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	defer cst.closeCst()
   134  
   135  	// Submit a file contract that is unrecognized.
   136  	_, err = cst.cs.storageProofSegment(types.FileContractID{})
   137  	if err != ErrUnrecognizedFileContractID {
   138  		t.Error(err)
   139  	}
   140  
   141  	// Try to get the segment of an unfinished file contract.
   142  	cst.cs.db.addFileContracts(types.FileContractID{}, types.FileContract{
   143  		WindowStart: 100000,
   144  	})
   145  	_, err = cst.cs.storageProofSegment(types.FileContractID{})
   146  	if err != ErrUnfinishedFileContract {
   147  		t.Error(err)
   148  	}
   149  }
   150  
   151  // TestStorageProofSegmentRandomness checks that the storageProofSegment method
   152  // is producing outputs that pass an imperfect randomness check (gzip).
   153  func TestStorageProofSegmentRandomness(t *testing.T) {
   154  	t.Skip("randomness check takes a long time")
   155  	cst, err := createConsensusSetTester("TestStorageProofSegment")
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  	defer cst.closeCst()
   160  
   161  	// Add a file contract to the consensus set that can be used to probe the
   162  	// storage segment.
   163  	var outputs []byte
   164  	for i := 0; i < 32*256; i++ {
   165  		var fcid types.FileContractID
   166  		rand.Read(fcid[:])
   167  		fc := types.FileContract{
   168  			WindowStart: 2,
   169  			FileSize:    256 * 64,
   170  		}
   171  		cst.cs.db.addFileContracts(fcid, fc)
   172  		index, err := cst.cs.storageProofSegment(fcid)
   173  		if err != nil {
   174  			t.Error(err)
   175  		}
   176  		outputs = append(outputs, byte(index))
   177  	}
   178  
   179  	// Perform entropy testing on 'outputs' to verify randomness.
   180  	var b bytes.Buffer
   181  	zip := gzip.NewWriter(&b)
   182  	_, err = zip.Write(outputs)
   183  	if err != nil {
   184  		t.Fatal(err)
   185  	}
   186  	zip.Close()
   187  	if b.Len() < len(outputs) {
   188  		t.Error("supposedly high entropy random segments have been compressed!")
   189  	}
   190  }
   191  
   192  // TestValidStorageProofs probes the validStorageProofs method of the consensus
   193  // set.
   194  func TestValidStorageProofs(t *testing.T) {
   195  	if testing.Short() {
   196  		t.SkipNow()
   197  	}
   198  	cst, err := createConsensusSetTester("TestValidStorageProofs")
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	defer cst.closeCst()
   203  
   204  	// COMPATv0.4.0
   205  	//
   206  	// Mine 10 blocks so that the post-hardfork rules are in effect.
   207  	for i := 0; i < 10; i++ {
   208  		block, _ := cst.miner.FindBlock()
   209  		err = cst.cs.AcceptBlock(block)
   210  		if err != nil {
   211  			t.Fatal(err)
   212  		}
   213  	}
   214  
   215  	// Create a file contract for which a storage proof can be created.
   216  	var fcid types.FileContractID
   217  	fcid[0] = 12
   218  	simFile := make([]byte, 64*1024)
   219  	_, err = rand.Read(simFile)
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	buffer := bytes.NewReader(simFile)
   224  	root, err := crypto.ReaderMerkleRoot(buffer)
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	fc := types.FileContract{
   229  		FileSize:       64 * 1024,
   230  		FileMerkleRoot: root,
   231  		WindowStart:    2,
   232  		WindowEnd:      1200,
   233  	}
   234  	cst.cs.db.addFileContracts(fcid, fc)
   235  	buffer.Seek(0, 0)
   236  
   237  	// Create a transaction with a storage proof.
   238  	proofIndex, err := cst.cs.storageProofSegment(fcid)
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  	base, proofSet, err := crypto.BuildReaderProof(buffer, proofIndex)
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  	txn := types.Transaction{
   247  		StorageProofs: []types.StorageProof{{
   248  			ParentID: fcid,
   249  			HashSet:  proofSet,
   250  		}},
   251  	}
   252  	copy(txn.StorageProofs[0].Segment[:], base)
   253  	err = cst.cs.validStorageProofs(txn)
   254  	if err != nil {
   255  		t.Error(err)
   256  	}
   257  
   258  	// Corrupt the proof set.
   259  	proofSet[0][0]++
   260  	txn = types.Transaction{
   261  		StorageProofs: []types.StorageProof{{
   262  			ParentID: fcid,
   263  			HashSet:  proofSet,
   264  		}},
   265  	}
   266  	copy(txn.StorageProofs[0].Segment[:], base)
   267  	err = cst.cs.validStorageProofs(txn)
   268  	if err != ErrInvalidStorageProof {
   269  		t.Error(err)
   270  	}
   271  
   272  	// Try to validate a proof for a file contract that doesn't exist.
   273  	txn.StorageProofs[0].ParentID = types.FileContractID{}
   274  	err = cst.cs.validStorageProofs(txn)
   275  	if err != ErrUnrecognizedFileContractID {
   276  		t.Error(err)
   277  	}
   278  
   279  	// Try a proof set where there is padding on the last segment in the file.
   280  	file := make([]byte, 100)
   281  	_, err = rand.Read(file)
   282  	if err != nil {
   283  		t.Fatal(err)
   284  	}
   285  	buffer = bytes.NewReader(file)
   286  	root, err = crypto.ReaderMerkleRoot(buffer)
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	fc = types.FileContract{
   291  		FileSize:       100,
   292  		FileMerkleRoot: root,
   293  		WindowStart:    2,
   294  		WindowEnd:      1200,
   295  	}
   296  	buffer.Seek(0, 0)
   297  
   298  	// Find a proofIndex that has the value '1'.
   299  	for {
   300  		fcid[0]++
   301  		cst.cs.db.addFileContracts(fcid, fc)
   302  		proofIndex, err = cst.cs.storageProofSegment(fcid)
   303  		if err != nil {
   304  			t.Fatal(err)
   305  		}
   306  		if proofIndex == 1 {
   307  			break
   308  		}
   309  	}
   310  	base, proofSet, err = crypto.BuildReaderProof(buffer, proofIndex)
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	txn = types.Transaction{
   315  		StorageProofs: []types.StorageProof{{
   316  			ParentID: fcid,
   317  			HashSet:  proofSet,
   318  		}},
   319  	}
   320  	copy(txn.StorageProofs[0].Segment[:], base)
   321  	err = cst.cs.validStorageProofs(txn)
   322  	if err != nil {
   323  		t.Fatal(err)
   324  	}
   325  }
   326  
   327  // COMPATv0.4.0
   328  //
   329  // TestPreForkValidStorageProofs checks that storage proofs which are invalid
   330  // before the hardfork (but valid afterwards) are still rejected before the
   331  // hardfork).
   332  func TestPreForkValidStorageProofs(t *testing.T) {
   333  	if testing.Short() {
   334  		t.SkipNow()
   335  	}
   336  	cst, err := createConsensusSetTester("TestPreForkValidStorageProofs")
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  	defer cst.closeCst()
   341  
   342  	// Try a proof set where there is padding on the last segment in the file.
   343  	file := make([]byte, 100)
   344  	_, err = rand.Read(file)
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	}
   348  	buffer := bytes.NewReader(file)
   349  	root, err := crypto.ReaderMerkleRoot(buffer)
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  	fc := types.FileContract{
   354  		FileSize:       100,
   355  		FileMerkleRoot: root,
   356  		WindowStart:    2,
   357  		WindowEnd:      1200,
   358  	}
   359  	buffer.Seek(0, 0)
   360  
   361  	// Find a proofIndex that has the value '1'.
   362  	var fcid types.FileContractID
   363  	var proofIndex uint64
   364  	for {
   365  		fcid[0]++
   366  		cst.cs.db.addFileContracts(fcid, fc)
   367  		proofIndex, err = cst.cs.storageProofSegment(fcid)
   368  		if err != nil {
   369  			t.Fatal(err)
   370  		}
   371  		if proofIndex == 1 {
   372  			break
   373  		}
   374  	}
   375  	base, proofSet, err := crypto.BuildReaderProof(buffer, proofIndex)
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  	txn := types.Transaction{
   380  		StorageProofs: []types.StorageProof{{
   381  			ParentID: fcid,
   382  			HashSet:  proofSet,
   383  		}},
   384  	}
   385  	copy(txn.StorageProofs[0].Segment[:], base)
   386  	err = cst.cs.validStorageProofs(txn)
   387  	if err != ErrInvalidStorageProof {
   388  		t.Fatal(err)
   389  	}
   390  }
   391  
   392  // TestValidFileContractRevisions probes the validFileContractRevisions method
   393  // of the consensus set.
   394  func TestValidFileContractRevisions(t *testing.T) {
   395  	if testing.Short() {
   396  		t.SkipNow()
   397  	}
   398  	cst, err := createConsensusSetTester("TestValidFileContractRevisions")
   399  	if err != nil {
   400  		t.Fatal(err)
   401  	}
   402  	defer cst.closeCst()
   403  
   404  	// Grab an address + unlock conditions for the transaction.
   405  	unlockConditions, err := cst.wallet.NextAddress()
   406  	if err != nil {
   407  		t.Fatal(err)
   408  	}
   409  
   410  	// Create a file contract for which a storage proof can be created.
   411  	var fcid types.FileContractID
   412  	fcid[0] = 12
   413  	simFile := make([]byte, 64*1024)
   414  	rand.Read(simFile)
   415  	buffer := bytes.NewReader(simFile)
   416  	root, err := crypto.ReaderMerkleRoot(buffer)
   417  	if err != nil {
   418  		t.Fatal(err)
   419  	}
   420  	fc := types.FileContract{
   421  		FileSize:       64 * 1024,
   422  		FileMerkleRoot: root,
   423  		WindowStart:    102,
   424  		WindowEnd:      1200,
   425  		UnlockHash:     unlockConditions.UnlockHash(),
   426  		RevisionNumber: 1,
   427  	}
   428  	cst.cs.db.addFileContracts(fcid, fc)
   429  
   430  	// Try a working file contract revision.
   431  	txn := types.Transaction{
   432  		FileContractRevisions: []types.FileContractRevision{
   433  			{
   434  				ParentID:          fcid,
   435  				UnlockConditions:  unlockConditions,
   436  				NewRevisionNumber: 2,
   437  			},
   438  		},
   439  	}
   440  	err = cst.cs.validFileContractRevisions(txn)
   441  	if err != nil {
   442  		t.Error(err)
   443  	}
   444  
   445  	// Try a transaction with an insufficient revision number.
   446  	txn = types.Transaction{
   447  		FileContractRevisions: []types.FileContractRevision{
   448  			{
   449  				ParentID:          fcid,
   450  				UnlockConditions:  unlockConditions,
   451  				NewRevisionNumber: 1,
   452  			},
   453  		},
   454  	}
   455  	err = cst.cs.validFileContractRevisions(txn)
   456  	if err != ErrLowRevisionNumber {
   457  		t.Error(err)
   458  	}
   459  	txn = types.Transaction{
   460  		FileContractRevisions: []types.FileContractRevision{
   461  			{
   462  				ParentID:          fcid,
   463  				UnlockConditions:  unlockConditions,
   464  				NewRevisionNumber: 0,
   465  			},
   466  		},
   467  	}
   468  	err = cst.cs.validFileContractRevisions(txn)
   469  	if err != ErrLowRevisionNumber {
   470  		t.Error(err)
   471  	}
   472  
   473  	// Submit a file contract revision pointing to an invalid parent.
   474  	txn.FileContractRevisions[0].ParentID[0]--
   475  	err = cst.cs.validFileContractRevisions(txn)
   476  	if err != ErrUnrecognizedFileContractID {
   477  		t.Error(err)
   478  	}
   479  	txn.FileContractRevisions[0].ParentID[0]++
   480  
   481  	// Submit a file contract revision for a file contract whose window has
   482  	// already opened.
   483  	fc = cst.cs.db.getFileContracts(fcid)
   484  	fc.WindowStart = 0
   485  	cst.cs.db.rmFileContracts(fcid)
   486  	cst.cs.db.addFileContracts(fcid, fc)
   487  	txn.FileContractRevisions[0].NewRevisionNumber = 3
   488  	err = cst.cs.validFileContractRevisions(txn)
   489  	if err != ErrLateRevision {
   490  		t.Error(err)
   491  	}
   492  
   493  	// Submit a file contract revision with incorrect unlock conditions.
   494  	fc.WindowStart = 100
   495  	cst.cs.db.rmFileContracts(fcid)
   496  	cst.cs.db.addFileContracts(fcid, fc)
   497  	txn.FileContractRevisions[0].UnlockConditions.Timelock++
   498  	err = cst.cs.validFileContractRevisions(txn)
   499  	if err != ErrWrongUnlockConditions {
   500  		t.Error(err)
   501  	}
   502  	txn.FileContractRevisions[0].UnlockConditions.Timelock--
   503  
   504  	// Submit file contract revisions for file contracts with altered payouts.
   505  	txn.FileContractRevisions[0].NewValidProofOutputs = []types.SiacoinOutput{{
   506  		Value: types.NewCurrency64(1),
   507  	}}
   508  	txn.FileContractRevisions[0].NewMissedProofOutputs = []types.SiacoinOutput{{
   509  		Value: types.NewCurrency64(1),
   510  	}}
   511  	err = cst.cs.validFileContractRevisions(txn)
   512  	if err != ErrAlteredRevisionPayouts {
   513  		t.Error(err)
   514  	}
   515  	txn.FileContractRevisions[0].NewValidProofOutputs = nil
   516  	err = cst.cs.validFileContractRevisions(txn)
   517  	if err != ErrAlteredRevisionPayouts {
   518  		t.Error(err)
   519  	}
   520  	txn.FileContractRevisions[0].NewValidProofOutputs = []types.SiacoinOutput{{
   521  		Value: types.NewCurrency64(1),
   522  	}}
   523  	txn.FileContractRevisions[0].NewMissedProofOutputs = nil
   524  	err = cst.cs.validFileContractRevisions(txn)
   525  	if err != ErrAlteredRevisionPayouts {
   526  		t.Error(err)
   527  	}
   528  }
   529  
   530  // TestValidSiafunds probes the validSiafunds mthod of the consensus set.
   531  func TestValidSiafunds(t *testing.T) {
   532  	if testing.Short() {
   533  		t.SkipNow()
   534  	}
   535  	cst, err := createConsensusSetTester("TestValidSiafunds")
   536  	if err != nil {
   537  		t.Fatal(err)
   538  	}
   539  	defer cst.closeCst()
   540  
   541  	// Create a transaction pointing to a nonexistent siafund output.
   542  	txn := types.Transaction{
   543  		SiafundInputs: []types.SiafundInput{{}},
   544  	}
   545  	err = cst.cs.validSiafunds(txn)
   546  	if err != ErrMissingSiafundOutput {
   547  		t.Error(err)
   548  	}
   549  
   550  	// Create a transaction with invalid unlock conditions.
   551  	var sfoid types.SiafundOutputID
   552  	cst.cs.db.forEachSiafundOutputs(func(mapSfoid types.SiafundOutputID, sfo types.SiafundOutput) {
   553  		sfoid = mapSfoid
   554  		// pointless to do this but I can't think of a better way.
   555  	})
   556  	txn = types.Transaction{
   557  		SiafundInputs: []types.SiafundInput{{
   558  			ParentID:         sfoid,
   559  			UnlockConditions: types.UnlockConditions{Timelock: 12345}, // avoid collisions with existing outputs
   560  		}},
   561  	}
   562  	err = cst.cs.validSiafunds(txn)
   563  	if err != ErrWrongUnlockConditions {
   564  		t.Error(err)
   565  	}
   566  
   567  	// Create a transaction with more outputs than inputs.
   568  	txn = types.Transaction{
   569  		SiafundOutputs: []types.SiafundOutput{{
   570  			Value: types.NewCurrency64(1),
   571  		}},
   572  	}
   573  	err = cst.cs.validSiafunds(txn)
   574  	if err != ErrSiafundInputOutputMismatch {
   575  		t.Error(err)
   576  	}
   577  }
   578  
   579  // TestValidTransaction probes the validTransaction method of the consensus
   580  // set.
   581  func TestValidTransaction(t *testing.T) {
   582  	if testing.Short() {
   583  		t.SkipNow()
   584  	}
   585  	cst, err := createConsensusSetTester("TestValidTransaction")
   586  	if err != nil {
   587  		t.Fatal(err)
   588  	}
   589  	defer cst.closeCst()
   590  
   591  	// Create a transaction that is not standalone valid.
   592  	txn := types.Transaction{
   593  		FileContracts: []types.FileContract{{
   594  			WindowStart: 0,
   595  		}},
   596  	}
   597  	err = cst.cs.validTransaction(txn)
   598  	if err == nil {
   599  		t.Error("transaction is valid")
   600  	}
   601  
   602  	// Create a transaction with invalid siacoins.
   603  	txn = types.Transaction{
   604  		SiacoinInputs: []types.SiacoinInput{{}},
   605  	}
   606  	err = cst.cs.validTransaction(txn)
   607  	if err == nil {
   608  		t.Error("transaction is valid")
   609  	}
   610  
   611  	// Create a transaction with invalid storage proofs.
   612  	txn = types.Transaction{
   613  		StorageProofs: []types.StorageProof{{}},
   614  	}
   615  	err = cst.cs.validTransaction(txn)
   616  	if err == nil {
   617  		t.Error("transaction is valid")
   618  	}
   619  
   620  	// Create a transaction with invalid file contract revisions.
   621  	txn = types.Transaction{
   622  		FileContractRevisions: []types.FileContractRevision{{
   623  			NewWindowStart: 5000,
   624  			NewWindowEnd:   5005,
   625  			ParentID:       types.FileContractID{},
   626  		}},
   627  	}
   628  	err = cst.cs.validTransaction(txn)
   629  	if err == nil {
   630  		t.Error("transaction is valid")
   631  	}
   632  
   633  	// Create a transaction with invalid siafunds.
   634  	txn = types.Transaction{
   635  		SiafundInputs: []types.SiafundInput{{}},
   636  	}
   637  	err = cst.cs.validTransaction(txn)
   638  	if err == nil {
   639  		t.Error("transaction is valid")
   640  	}
   641  }
   642  */