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

     1  package consensus
     2  
     3  /*
     4  import (
     5  	"testing"
     6  
     7  	"github.com/NebulousLabs/Sia/modules"
     8  	"github.com/NebulousLabs/Sia/types"
     9  )
    10  
    11  // TestApplySiacoinInputs probes the applySiacoinInputs method of the consensus
    12  // set.
    13  func TestApplySiacoinInputs(t *testing.T) {
    14  	if testing.Short() {
    15  		t.SkipNow()
    16  	}
    17  
    18  	// Create a consensus set and get it to 3 siacoin outputs. The consensus
    19  	// set starts with 2 siacoin outputs, mining a block will add another.
    20  	cst, err := createConsensusSetTester(t.Name())
    21  	if err != nil {
    22  		t.Fatal(err)
    23  	}
    24  	defer cst.closeCst()
    25  	b, _ := cst.miner.FindBlock()
    26  	err = cst.cs.AcceptBlock(b)
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  
    31  	// Create a block node to use with application.
    32  	pb := new(processedBlock)
    33  
    34  	// Fetch the output id's of each siacoin output in the consensus set.
    35  	var ids []types.SiacoinOutputID
    36  	cst.cs.db.forEachSiacoinOutputs(func(id types.SiacoinOutputID, sco types.SiacoinOutput) {
    37  		ids = append(ids, id)
    38  	})
    39  
    40  	// Apply a transaction with a single siacoin input.
    41  	txn := types.Transaction{
    42  		SiacoinInputs: []types.SiacoinInput{
    43  			{ParentID: ids[0]},
    44  		},
    45  	}
    46  	cst.cs.applySiacoinInputs(pb, txn)
    47  	exists := cst.cs.db.inSiacoinOutputs(ids[0])
    48  	if exists {
    49  		t.Error("Failed to conusme a siacoin output")
    50  	}
    51  	if cst.cs.db.lenSiacoinOutputs() != 2 {
    52  		t.Error("siacoin outputs not correctly updated")
    53  	}
    54  	if len(pb.SiacoinOutputDiffs) != 1 {
    55  		t.Error("block node was not updated for single transaction")
    56  	}
    57  	if pb.SiacoinOutputDiffs[0].Direction != modules.DiffRevert {
    58  		t.Error("wrong diff direction applied when consuming a siacoin output")
    59  	}
    60  	if pb.SiacoinOutputDiffs[0].ID != ids[0] {
    61  		t.Error("wrong id used when consuming a siacoin output")
    62  	}
    63  
    64  	// Apply a transaction with two siacoin inputs.
    65  	txn = types.Transaction{
    66  		SiacoinInputs: []types.SiacoinInput{
    67  			{ParentID: ids[1]},
    68  			{ParentID: ids[2]},
    69  		},
    70  	}
    71  	cst.cs.applySiacoinInputs(pb, txn)
    72  	if cst.cs.db.lenSiacoinOutputs() != 0 {
    73  		t.Error("failed to consume all siacoin outputs in the consensus set")
    74  	}
    75  	if len(pb.SiacoinOutputDiffs) != 3 {
    76  		t.Error("processed block was not updated for single transaction")
    77  	}
    78  }
    79  
    80  // TestMisuseApplySiacoinInputs misuses applySiacoinInput and checks that a
    81  // panic was triggered.
    82  func TestMisuseApplySiacoinInputs(t *testing.T) {
    83  	if testing.Short() {
    84  		t.SkipNow()
    85  	}
    86  	cst, err := createConsensusSetTester(t.Name())
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  	defer cst.closeCst()
    91  
    92  	// Create a block node to use with application.
    93  	pb := new(processedBlock)
    94  
    95  	// Fetch the output id's of each siacoin output in the consensus set.
    96  	var ids []types.SiacoinOutputID
    97  	cst.cs.db.forEachSiacoinOutputs(func(id types.SiacoinOutputID, sco types.SiacoinOutput) {
    98  		ids = append(ids, id)
    99  	})
   100  
   101  	// Apply a transaction with a single siacoin input.
   102  	txn := types.Transaction{
   103  		SiacoinInputs: []types.SiacoinInput{
   104  			{ParentID: ids[0]},
   105  		},
   106  	}
   107  	cst.cs.applySiacoinInputs(pb, txn)
   108  
   109  	// Trigger the panic that occurs when an output is applied incorrectly, and
   110  	// perform a catch to read the error that is created.
   111  	defer func() {
   112  		r := recover()
   113  		if r == nil {
   114  			t.Error("expecting error after corrupting database")
   115  		}
   116  	}()
   117  	cst.cs.applySiacoinInputs(pb, txn)
   118  }
   119  
   120  // TestApplySiacoinOutputs probes the applySiacoinOutput method of the
   121  // consensus set.
   122  func TestApplySiacoinOutputs(t *testing.T) {
   123  	if testing.Short() {
   124  		t.SkipNow()
   125  	}
   126  	cst, err := createConsensusSetTester(t.Name())
   127  	if err != nil {
   128  		t.Fatal(err)
   129  	}
   130  	defer cst.closeCst()
   131  
   132  	// Create a block node to use with application.
   133  	pb := new(processedBlock)
   134  
   135  	// Apply a transaction with a single siacoin output.
   136  	txn := types.Transaction{
   137  		SiacoinOutputs: []types.SiacoinOutput{{}},
   138  	}
   139  	cst.cs.applySiacoinOutputs(pb, txn)
   140  	scoid := txn.SiacoinOutputID(0)
   141  	exists := cst.cs.db.inSiacoinOutputs(scoid)
   142  	if !exists {
   143  		t.Error("Failed to create siacoin output")
   144  	}
   145  	if cst.cs.db.lenSiacoinOutputs() != 3 { // 3 because createConsensusSetTester has 2 initially.
   146  		t.Error("siacoin outputs not correctly updated")
   147  	}
   148  	if len(pb.SiacoinOutputDiffs) != 1 {
   149  		t.Error("block node was not updated for single element transaction")
   150  	}
   151  	if pb.SiacoinOutputDiffs[0].Direction != modules.DiffApply {
   152  		t.Error("wrong diff direction applied when creating a siacoin output")
   153  	}
   154  	if pb.SiacoinOutputDiffs[0].ID != scoid {
   155  		t.Error("wrong id used when creating a siacoin output")
   156  	}
   157  
   158  	// Apply a transaction with 2 siacoin outputs.
   159  	txn = types.Transaction{
   160  		SiacoinOutputs: []types.SiacoinOutput{
   161  			{Value: types.NewCurrency64(1)},
   162  			{Value: types.NewCurrency64(2)},
   163  		},
   164  	}
   165  	cst.cs.applySiacoinOutputs(pb, txn)
   166  	scoid0 := txn.SiacoinOutputID(0)
   167  	scoid1 := txn.SiacoinOutputID(1)
   168  	exists = cst.cs.db.inSiacoinOutputs(scoid0)
   169  	if !exists {
   170  		t.Error("Failed to create siacoin output")
   171  	}
   172  	exists = cst.cs.db.inSiacoinOutputs(scoid1)
   173  	if !exists {
   174  		t.Error("Failed to create siacoin output")
   175  	}
   176  	if cst.cs.db.lenSiacoinOutputs() != 5 { // 5 because createConsensusSetTester has 2 initially.
   177  		t.Error("siacoin outputs not correctly updated")
   178  	}
   179  	if len(pb.SiacoinOutputDiffs) != 3 {
   180  		t.Error("block node was not updated correctly")
   181  	}
   182  }
   183  
   184  // TestMisuseApplySiacoinOutputs misuses applySiacoinOutputs and checks that a
   185  // panic was triggered.
   186  func TestMisuseApplySiacoinOutputs(t *testing.T) {
   187  	if testing.Short() {
   188  		t.SkipNow()
   189  	}
   190  	cst, err := createConsensusSetTester(t.Name())
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	defer cst.closeCst()
   195  
   196  	// Create a block node to use with application.
   197  	pb := new(processedBlock)
   198  
   199  	// Apply a transaction with a single siacoin output.
   200  	txn := types.Transaction{
   201  		SiacoinOutputs: []types.SiacoinOutput{{}},
   202  	}
   203  	cst.cs.applySiacoinOutputs(pb, txn)
   204  
   205  	// Trigger the panic that occurs when an output is applied incorrectly, and
   206  	// perform a catch to read the error that is created.
   207  	defer func() {
   208  		r := recover()
   209  		if r == nil {
   210  			t.Error("no panic occurred when misusing applySiacoinInput")
   211  		}
   212  	}()
   213  	cst.cs.applySiacoinOutputs(pb, txn)
   214  }
   215  
   216  // TestApplyFileContracts probes the applyFileContracts method of the
   217  // consensus set.
   218  func TestApplyFileContracts(t *testing.T) {
   219  	if testing.Short() {
   220  		t.SkipNow()
   221  	}
   222  	cst, err := createConsensusSetTester(t.Name())
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	defer cst.closeCst()
   227  
   228  	// Create a block node to use with application.
   229  	pb := new(processedBlock)
   230  
   231  	// Apply a transaction with a single file contract.
   232  	txn := types.Transaction{
   233  		FileContracts: []types.FileContract{{}},
   234  	}
   235  	cst.cs.applyFileContracts(pb, txn)
   236  	fcid := txn.FileContractID(0)
   237  	exists := cst.cs.db.inFileContracts(fcid)
   238  	if !exists {
   239  		t.Error("Failed to create file contract")
   240  	}
   241  	if cst.cs.db.lenFileContracts() != 1 {
   242  		t.Error("file contracts not correctly updated")
   243  	}
   244  	if len(pb.FileContractDiffs) != 1 {
   245  		t.Error("block node was not updated for single element transaction")
   246  	}
   247  	if pb.FileContractDiffs[0].Direction != modules.DiffApply {
   248  		t.Error("wrong diff direction applied when creating a file contract")
   249  	}
   250  	if pb.FileContractDiffs[0].ID != fcid {
   251  		t.Error("wrong id used when creating a file contract")
   252  	}
   253  
   254  	// Apply a transaction with 2 file contracts.
   255  	txn = types.Transaction{
   256  		FileContracts: []types.FileContract{
   257  			{Payout: types.NewCurrency64(1)},
   258  			{Payout: types.NewCurrency64(300e3)},
   259  		},
   260  	}
   261  	cst.cs.applyFileContracts(pb, txn)
   262  	fcid0 := txn.FileContractID(0)
   263  	fcid1 := txn.FileContractID(1)
   264  	exists = cst.cs.db.inFileContracts(fcid0)
   265  	if !exists {
   266  		t.Error("Failed to create file contract")
   267  	}
   268  	exists = cst.cs.db.inFileContracts(fcid1)
   269  	if !exists {
   270  		t.Error("Failed to create file contract")
   271  	}
   272  	if cst.cs.db.lenFileContracts() != 3 {
   273  		t.Error("file contracts not correctly updated")
   274  	}
   275  	if len(pb.FileContractDiffs) != 3 {
   276  		t.Error("block node was not updated correctly")
   277  	}
   278  	if cst.cs.siafundPool.Cmp64(10e3) != 0 {
   279  		t.Error("siafund pool did not update correctly upon creation of a file contract")
   280  	}
   281  }
   282  
   283  // TestMisuseApplyFileContracts misuses applyFileContracts and checks that a
   284  // panic was triggered.
   285  func TestMisuseApplyFileContracts(t *testing.T) {
   286  	if testing.Short() {
   287  		t.SkipNow()
   288  	}
   289  	cst, err := createConsensusSetTester(t.Name())
   290  	if err != nil {
   291  		t.Fatal(err)
   292  	}
   293  	defer cst.closeCst()
   294  
   295  	// Create a block node to use with application.
   296  	pb := new(processedBlock)
   297  
   298  	// Apply a transaction with a single file contract.
   299  	txn := types.Transaction{
   300  		FileContracts: []types.FileContract{{}},
   301  	}
   302  	cst.cs.applyFileContracts(pb, txn)
   303  
   304  	// Trigger the panic that occurs when an output is applied incorrectly, and
   305  	// perform a catch to read the error that is created.
   306  	defer func() {
   307  		r := recover()
   308  		if r == nil {
   309  			t.Error("no panic occurred when misusing applySiacoinInput")
   310  		}
   311  	}()
   312  	cst.cs.applyFileContracts(pb, txn)
   313  }
   314  
   315  // TestApplyFileContractRevisions probes the applyFileContractRevisions method
   316  // of the consensus set.
   317  func TestApplyFileContractRevisions(t *testing.T) {
   318  	if testing.Short() {
   319  		t.SkipNow()
   320  	}
   321  	cst, err := createConsensusSetTester(t.Name())
   322  	if err != nil {
   323  		t.Fatal(err)
   324  	}
   325  	defer cst.closeCst()
   326  
   327  	// Create a block node to use with application.
   328  	pb := new(processedBlock)
   329  
   330  	// Apply a transaction with two file contracts - that way there is
   331  	// something to revise.
   332  	txn := types.Transaction{
   333  		FileContracts: []types.FileContract{
   334  			{},
   335  			{Payout: types.NewCurrency64(1)},
   336  		},
   337  	}
   338  	cst.cs.applyFileContracts(pb, txn)
   339  	fcid0 := txn.FileContractID(0)
   340  	fcid1 := txn.FileContractID(1)
   341  
   342  	// Apply a single file contract revision.
   343  	txn = types.Transaction{
   344  		FileContractRevisions: []types.FileContractRevision{
   345  			{
   346  				ParentID:    fcid0,
   347  				NewFileSize: 1,
   348  			},
   349  		},
   350  	}
   351  	cst.cs.applyFileContractRevisions(pb, txn)
   352  	exists := cst.cs.db.inFileContracts(fcid0)
   353  	if !exists {
   354  		t.Error("Revision killed a file contract")
   355  	}
   356  	fc := cst.cs.db.getFileContracts(fcid0)
   357  	if fc.FileSize != 1 {
   358  		t.Error("file contract filesize not properly updated")
   359  	}
   360  	if cst.cs.db.lenFileContracts() != 2 {
   361  		t.Error("file contracts not correctly updated")
   362  	}
   363  	if len(pb.FileContractDiffs) != 4 { // 2 creating the initial contracts, 1 to remove the old, 1 to add the revision.
   364  		t.Error("block node was not updated for single element transaction")
   365  	}
   366  	if pb.FileContractDiffs[2].Direction != modules.DiffRevert {
   367  		t.Error("wrong diff direction applied when revising a file contract")
   368  	}
   369  	if pb.FileContractDiffs[3].Direction != modules.DiffApply {
   370  		t.Error("wrong diff direction applied when revising a file contract")
   371  	}
   372  	if pb.FileContractDiffs[2].ID != fcid0 {
   373  		t.Error("wrong id used when revising a file contract")
   374  	}
   375  	if pb.FileContractDiffs[3].ID != fcid0 {
   376  		t.Error("wrong id used when revising a file contract")
   377  	}
   378  
   379  	// Apply a transaction with 2 file contract revisions.
   380  	txn = types.Transaction{
   381  		FileContractRevisions: []types.FileContractRevision{
   382  			{
   383  				ParentID:    fcid0,
   384  				NewFileSize: 2,
   385  			},
   386  			{
   387  				ParentID:    fcid1,
   388  				NewFileSize: 3,
   389  			},
   390  		},
   391  	}
   392  	cst.cs.applyFileContractRevisions(pb, txn)
   393  	exists = cst.cs.db.inFileContracts(fcid0)
   394  	if !exists {
   395  		t.Error("Revision ate file contract")
   396  	}
   397  	fc0 := cst.cs.db.getFileContracts(fcid0)
   398  	exists = cst.cs.db.inFileContracts(fcid1)
   399  	if !exists {
   400  		t.Error("Revision ate file contract")
   401  	}
   402  	fc1 := cst.cs.db.getFileContracts(fcid1)
   403  	if fc0.FileSize != 2 {
   404  		t.Error("Revision not correctly applied")
   405  	}
   406  	if fc1.FileSize != 3 {
   407  		t.Error("Revision not correctly applied")
   408  	}
   409  	if cst.cs.db.lenFileContracts() != 2 {
   410  		t.Error("file contracts not correctly updated")
   411  	}
   412  	if len(pb.FileContractDiffs) != 8 {
   413  		t.Error("block node was not updated correctly")
   414  	}
   415  }
   416  
   417  // TestMisuseApplyFileContractRevisions misuses applyFileContractRevisions and
   418  // checks that a panic was triggered.
   419  func TestMisuseApplyFileContractRevisions(t *testing.T) {
   420  	if testing.Short() {
   421  		t.SkipNow()
   422  	}
   423  	cst, err := createConsensusSetTester(t.Name())
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	defer cst.closeCst()
   428  
   429  	// Create a block node to use with application.
   430  	pb := new(processedBlock)
   431  
   432  	// Trigger a panic from revising a nonexistent file contract.
   433  	defer func() {
   434  		r := recover()
   435  		if r != errNilItem {
   436  			t.Error("no panic occurred when misusing applySiacoinInput")
   437  		}
   438  	}()
   439  	txn := types.Transaction{
   440  		FileContractRevisions: []types.FileContractRevision{{}},
   441  	}
   442  	cst.cs.applyFileContractRevisions(pb, txn)
   443  }
   444  
   445  // TestApplyStorageProofs probes the applyStorageProofs method of the consensus
   446  // set.
   447  func TestApplyStorageProofs(t *testing.T) {
   448  	if testing.Short() {
   449  		t.SkipNow()
   450  	}
   451  	cst, err := createConsensusSetTester(t.Name())
   452  	if err != nil {
   453  		t.Fatal(err)
   454  	}
   455  	defer cst.closeCst()
   456  
   457  	// Create a block node to use with application.
   458  	pb := new(processedBlock)
   459  	pb.Height = cst.cs.height()
   460  
   461  	// Apply a transaction with two file contracts - there is a reason to
   462  	// create a storage proof.
   463  	txn := types.Transaction{
   464  		FileContracts: []types.FileContract{
   465  			{
   466  				Payout: types.NewCurrency64(300e3),
   467  				ValidProofOutputs: []types.SiacoinOutput{
   468  					{Value: types.NewCurrency64(290e3)},
   469  				},
   470  			},
   471  			{},
   472  			{
   473  				Payout: types.NewCurrency64(600e3),
   474  				ValidProofOutputs: []types.SiacoinOutput{
   475  					{Value: types.NewCurrency64(280e3)},
   476  					{Value: types.NewCurrency64(300e3)},
   477  				},
   478  			},
   479  		},
   480  	}
   481  	cst.cs.applyFileContracts(pb, txn)
   482  	fcid0 := txn.FileContractID(0)
   483  	fcid1 := txn.FileContractID(1)
   484  	fcid2 := txn.FileContractID(2)
   485  
   486  	// Apply a single storage proof.
   487  	txn = types.Transaction{
   488  		StorageProofs: []types.StorageProof{{ParentID: fcid0}},
   489  	}
   490  	cst.cs.applyStorageProofs(pb, txn)
   491  	exists := cst.cs.db.inFileContracts(fcid0)
   492  	if exists {
   493  		t.Error("Storage proof did not disable a file contract.")
   494  	}
   495  	if cst.cs.db.lenFileContracts() != 2 {
   496  		t.Error("file contracts not correctly updated")
   497  	}
   498  	if len(pb.FileContractDiffs) != 4 { // 3 creating the initial contracts, 1 for the storage proof.
   499  		t.Error("block node was not updated for single element transaction")
   500  	}
   501  	if pb.FileContractDiffs[3].Direction != modules.DiffRevert {
   502  		t.Error("wrong diff direction applied when revising a file contract")
   503  	}
   504  	if pb.FileContractDiffs[3].ID != fcid0 {
   505  		t.Error("wrong id used when revising a file contract")
   506  	}
   507  	spoid0 := fcid0.StorageProofOutputID(types.ProofValid, 0)
   508  	exists = cst.cs.db.inDelayedSiacoinOutputsHeight(pb.Height+types.MaturityDelay, spoid0)
   509  	if !exists {
   510  		t.Error("storage proof output not created after applying a storage proof")
   511  	}
   512  	sco := cst.cs.db.getDelayedSiacoinOutputs(pb.Height+types.MaturityDelay, spoid0)
   513  	if sco.Value.Cmp64(290e3) != 0 {
   514  		t.Error("storage proof output was created with the wrong value")
   515  	}
   516  
   517  	// Apply a transaction with 2 storage proofs.
   518  	txn = types.Transaction{
   519  		StorageProofs: []types.StorageProof{
   520  			{ParentID: fcid1},
   521  			{ParentID: fcid2},
   522  		},
   523  	}
   524  	cst.cs.applyStorageProofs(pb, txn)
   525  	exists = cst.cs.db.inFileContracts(fcid1)
   526  	if exists {
   527  		t.Error("Storage proof failed to consume file contract.")
   528  	}
   529  	exists = cst.cs.db.inFileContracts(fcid2)
   530  	if exists {
   531  		t.Error("storage proof did not consume file contract")
   532  	}
   533  	if cst.cs.db.lenFileContracts() != 0 {
   534  		t.Error("file contracts not correctly updated")
   535  	}
   536  	if len(pb.FileContractDiffs) != 6 {
   537  		t.Error("block node was not updated correctly")
   538  	}
   539  	spoid1 := fcid1.StorageProofOutputID(types.ProofValid, 0)
   540  	exists = cst.cs.db.inSiacoinOutputs(spoid1)
   541  	if exists {
   542  		t.Error("output created when file contract had no corresponding output")
   543  	}
   544  	spoid2 := fcid2.StorageProofOutputID(types.ProofValid, 0)
   545  	exists = cst.cs.db.inDelayedSiacoinOutputsHeight(pb.Height+types.MaturityDelay, spoid2)
   546  	if !exists {
   547  		t.Error("no output created by first output of file contract")
   548  	}
   549  	sco = cst.cs.db.getDelayedSiacoinOutputs(pb.Height+types.MaturityDelay, spoid2)
   550  	if sco.Value.Cmp64(280e3) != 0 {
   551  		t.Error("first siacoin output created has wrong value")
   552  	}
   553  	spoid3 := fcid2.StorageProofOutputID(types.ProofValid, 1)
   554  	exists = cst.cs.db.inDelayedSiacoinOutputsHeight(pb.Height+types.MaturityDelay, spoid3)
   555  	if !exists {
   556  		t.Error("second output not created for storage proof")
   557  	}
   558  	sco = cst.cs.db.getDelayedSiacoinOutputs(pb.Height+types.MaturityDelay, spoid3)
   559  	if sco.Value.Cmp64(300e3) != 0 {
   560  		t.Error("second siacoin output has wrong value")
   561  	}
   562  	if cst.cs.siafundPool.Cmp64(30e3) != 0 {
   563  		t.Error("siafund pool not being added up correctly")
   564  	}
   565  }
   566  
   567  // TestNonexistentStorageProof applies a storage proof which points to a
   568  // nonextentent parent.
   569  func TestNonexistentStorageProof(t *testing.T) {
   570  	if testing.Short() {
   571  		t.SkipNow()
   572  	}
   573  	cst, err := createConsensusSetTester(t.Name())
   574  	if err != nil {
   575  		t.Fatal(err)
   576  	}
   577  	defer cst.closeCst()
   578  
   579  	// Create a block node to use with application.
   580  	pb := new(processedBlock)
   581  
   582  	// Trigger a panic by applying a storage proof for a nonexistent file
   583  	// contract.
   584  	defer func() {
   585  		r := recover()
   586  		if r != errNilItem {
   587  			t.Error("no panic occurred when misusing applySiacoinInput")
   588  		}
   589  	}()
   590  	txn := types.Transaction{
   591  		StorageProofs: []types.StorageProof{{}},
   592  	}
   593  	cst.cs.applyStorageProofs(pb, txn)
   594  }
   595  
   596  // TestDuplicateStorageProof applies a storage proof which has already been
   597  // applied.
   598  func TestDuplicateStorageProof(t *testing.T) {
   599  	if testing.Short() {
   600  		t.SkipNow()
   601  	}
   602  	cst, err := createConsensusSetTester(t.Name())
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  	defer cst.closeCst()
   607  
   608  	// Create a block node.
   609  	pb := new(processedBlock)
   610  	pb.Height = cst.cs.height()
   611  
   612  	// Create a file contract for the storage proof to prove.
   613  	txn0 := types.Transaction{
   614  		FileContracts: []types.FileContract{
   615  			{
   616  				Payout: types.NewCurrency64(300e3),
   617  				ValidProofOutputs: []types.SiacoinOutput{
   618  					{Value: types.NewCurrency64(290e3)},
   619  				},
   620  			},
   621  		},
   622  	}
   623  	cst.cs.applyFileContracts(pb, txn0)
   624  	fcid := txn0.FileContractID(0)
   625  
   626  	// Apply a single storage proof.
   627  	txn1 := types.Transaction{
   628  		StorageProofs: []types.StorageProof{{ParentID: fcid}},
   629  	}
   630  	cst.cs.applyStorageProofs(pb, txn1)
   631  
   632  	// Trigger a panic by applying the storage proof again.
   633  	defer func() {
   634  		r := recover()
   635  		if r != ErrDuplicateValidProofOutput {
   636  			t.Error("failed to trigger ErrDuplicateValidProofOutput:", r)
   637  		}
   638  	}()
   639  	cst.cs.applyFileContracts(pb, txn0) // File contract was consumed by the first proof.
   640  	cst.cs.applyStorageProofs(pb, txn1)
   641  }
   642  
   643  // TestApplySiafundInputs probes the applySiafundInputs method of the consensus
   644  // set.
   645  func TestApplySiafundInputs(t *testing.T) {
   646  	if testing.Short() {
   647  		t.SkipNow()
   648  	}
   649  	cst, err := createConsensusSetTester(t.Name())
   650  	if err != nil {
   651  		t.Fatal(err)
   652  	}
   653  	defer cst.closeCst()
   654  
   655  	// Create a block node to use with application.
   656  	pb := new(processedBlock)
   657  	pb.Height = cst.cs.height()
   658  
   659  	// Fetch the output id's of each siacoin output in the consensus set.
   660  	var ids []types.SiafundOutputID
   661  	cst.cs.db.forEachSiafundOutputs(func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) {
   662  		ids = append(ids, sfoid)
   663  	})
   664  
   665  	// Apply a transaction with a single siafund input.
   666  	txn := types.Transaction{
   667  		SiafundInputs: []types.SiafundInput{
   668  			{ParentID: ids[0]},
   669  		},
   670  	}
   671  	cst.cs.applySiafundInputs(pb, txn)
   672  	exists := cst.cs.db.inSiafundOutputs(ids[0])
   673  	if exists {
   674  		t.Error("Failed to conusme a siafund output")
   675  	}
   676  	if cst.cs.db.lenSiafundOutputs() != 2 {
   677  		t.Error("siafund outputs not correctly updated", cst.cs.db.lenSiafundOutputs())
   678  	}
   679  	if len(pb.SiafundOutputDiffs) != 1 {
   680  		t.Error("block node was not updated for single transaction")
   681  	}
   682  	if pb.SiafundOutputDiffs[0].Direction != modules.DiffRevert {
   683  		t.Error("wrong diff direction applied when consuming a siafund output")
   684  	}
   685  	if pb.SiafundOutputDiffs[0].ID != ids[0] {
   686  		t.Error("wrong id used when consuming a siafund output")
   687  	}
   688  	if cst.cs.db.lenDelayedSiacoinOutputsHeight(cst.cs.height()+types.MaturityDelay) != 2 { // 1 for a block subsidy, 1 for the siafund claim.
   689  		t.Error("siafund claim was not created")
   690  	}
   691  }
   692  
   693  // TestMisuseApplySiafundInputs misuses applySiafundInputs and checks that a
   694  // panic was triggered.
   695  func TestMisuseApplySiafundInputs(t *testing.T) {
   696  	if testing.Short() {
   697  		t.SkipNow()
   698  	}
   699  	cst, err := createConsensusSetTester(t.Name())
   700  	if err != nil {
   701  		t.Fatal(err)
   702  	}
   703  	defer cst.closeCst()
   704  
   705  	// Create a block node to use with application.
   706  	pb := new(processedBlock)
   707  	pb.Height = cst.cs.height()
   708  
   709  	// Fetch the output id's of each siacoin output in the consensus set.
   710  	var ids []types.SiafundOutputID
   711  	cst.cs.db.forEachSiafundOutputs(func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) {
   712  		ids = append(ids, sfoid)
   713  	})
   714  
   715  	// Apply a transaction with a single siafund input.
   716  	txn := types.Transaction{
   717  		SiafundInputs: []types.SiafundInput{
   718  			{ParentID: ids[0]},
   719  		},
   720  	}
   721  	cst.cs.applySiafundInputs(pb, txn)
   722  
   723  	// Trigger the panic that occurs when an output is applied incorrectly, and
   724  	// perform a catch to read the error that is created.
   725  	defer func() {
   726  		r := recover()
   727  		if r != ErrMisuseApplySiafundInput {
   728  			t.Error("no panic occurred when misusing applySiacoinInput")
   729  			t.Error(r)
   730  		}
   731  	}()
   732  	cst.cs.applySiafundInputs(pb, txn)
   733  }
   734  
   735  // TestApplySiafundOutputs probes the applySiafundOutputs method of the
   736  // consensus set.
   737  func TestApplySiafundOutputs(t *testing.T) {
   738  	if testing.Short() {
   739  		t.SkipNow()
   740  	}
   741  	cst, err := createConsensusSetTester(t.Name())
   742  	if err != nil {
   743  		t.Fatal(err)
   744  	}
   745  	defer cst.closeCst()
   746  	cst.cs.siafundPool = types.NewCurrency64(101)
   747  
   748  	// Create a block node to use with application.
   749  	pb := new(processedBlock)
   750  
   751  	// Apply a transaction with a single siafund output.
   752  	txn := types.Transaction{
   753  		SiafundOutputs: []types.SiafundOutput{{}},
   754  	}
   755  	cst.cs.applySiafundOutputs(pb, txn)
   756  	sfoid := txn.SiafundOutputID(0)
   757  	exists := cst.cs.db.inSiafundOutputs(sfoid)
   758  	if !exists {
   759  		t.Error("Failed to create siafund output")
   760  	}
   761  	if cst.cs.db.lenSiafundOutputs() != 4 {
   762  		t.Error("siafund outputs not correctly updated")
   763  	}
   764  	if len(pb.SiafundOutputDiffs) != 1 {
   765  		t.Error("block node was not updated for single element transaction")
   766  	}
   767  	if pb.SiafundOutputDiffs[0].Direction != modules.DiffApply {
   768  		t.Error("wrong diff direction applied when creating a siafund output")
   769  	}
   770  	if pb.SiafundOutputDiffs[0].ID != sfoid {
   771  		t.Error("wrong id used when creating a siafund output")
   772  	}
   773  	if pb.SiafundOutputDiffs[0].SiafundOutput.ClaimStart.Cmp64(101) != 0 {
   774  		t.Error("claim start set incorrectly when creating a siafund output")
   775  	}
   776  
   777  	// Apply a transaction with 2 siacoin outputs.
   778  	txn = types.Transaction{
   779  		SiafundOutputs: []types.SiafundOutput{
   780  			{Value: types.NewCurrency64(1)},
   781  			{Value: types.NewCurrency64(2)},
   782  		},
   783  	}
   784  	cst.cs.applySiafundOutputs(pb, txn)
   785  	sfoid0 := txn.SiafundOutputID(0)
   786  	sfoid1 := txn.SiafundOutputID(1)
   787  	exists = cst.cs.db.inSiafundOutputs(sfoid0)
   788  	if !exists {
   789  		t.Error("Failed to create siafund output")
   790  	}
   791  	exists = cst.cs.db.inSiafundOutputs(sfoid1)
   792  	if !exists {
   793  		t.Error("Failed to create siafund output")
   794  	}
   795  	if cst.cs.db.lenSiafundOutputs() != 6 {
   796  		t.Error("siafund outputs not correctly updated")
   797  	}
   798  	if len(pb.SiafundOutputDiffs) != 3 {
   799  		t.Error("block node was not updated for single element transaction")
   800  	}
   801  }
   802  
   803  // TestMisuseApplySiafundOutputs misuses applySiafundOutputs and checks that a
   804  // panic was triggered.
   805  func TestMisuseApplySiafundOutputs(t *testing.T) {
   806  	if testing.Short() {
   807  		t.SkipNow()
   808  	}
   809  	cst, err := createConsensusSetTester(t.Name())
   810  	if err != nil {
   811  		t.Fatal(err)
   812  	}
   813  	defer cst.closeCst()
   814  
   815  	// Create a block node to use with application.
   816  	pb := new(processedBlock)
   817  
   818  	// Apply a transaction with a single siacoin output.
   819  	txn := types.Transaction{
   820  		SiafundOutputs: []types.SiafundOutput{{}},
   821  	}
   822  	cst.cs.applySiafundOutputs(pb, txn)
   823  
   824  	// Trigger the panic that occurs when an output is applied incorrectly, and
   825  	// perform a catch to read the error that is created.
   826  	defer func() {
   827  		r := recover()
   828  		if r != ErrMisuseApplySiafundOutput {
   829  			t.Error("no panic occurred when misusing applySiafundInput")
   830  		}
   831  	}()
   832  	cst.cs.applySiafundOutputs(pb, txn)
   833  }
   834  */