gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/contract_test.go (about)

     1  package proto
     2  
     3  import (
     4  	"bytes"
     5  	"math"
     6  	"os"
     7  	"path/filepath"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  
    12  	"gitlab.com/NebulousLabs/encoding"
    13  	"gitlab.com/NebulousLabs/fastrand"
    14  	"gitlab.com/NebulousLabs/ratelimit"
    15  	"gitlab.com/SkynetLabs/skyd/build"
    16  	"gitlab.com/SkynetLabs/skyd/skymodules"
    17  	"go.sia.tech/siad/crypto"
    18  	"go.sia.tech/siad/modules"
    19  	"go.sia.tech/siad/types"
    20  )
    21  
    22  // dependencyIgnoreInvalidUpdate will prevent a critical in NewContractSet during testing when an invalid update is encountered.
    23  type dependencyIgnoreInvalidUpdate struct {
    24  	modules.ProductionDependencies
    25  }
    26  
    27  // Disrupt returns true if the correct string is provided.
    28  func (d *dependencyIgnoreInvalidUpdate) Disrupt(s string) bool {
    29  	return s == "IgnoreInvalidUpdate"
    30  }
    31  
    32  // dependencyInterruptContractInsertion will interrupt inserting a contract
    33  // after writing the header but before writing the roots.
    34  type dependencyInterruptContractInsertion struct {
    35  	modules.ProductionDependencies
    36  }
    37  
    38  // Disrupt returns true if the correct string is provided.
    39  func (d *dependencyInterruptContractInsertion) Disrupt(s string) bool {
    40  	return s == "InterruptContractInsertion"
    41  }
    42  
    43  // TestContractUncommittedTxn tests that if a contract revision is left in an
    44  // uncommitted state, either version of the contract can be recovered.
    45  func TestContractUncommittedTxn(t *testing.T) {
    46  	// initial header every subtests starts out with.
    47  	initialHeader := contractHeader{
    48  		Transaction: types.Transaction{
    49  			FileContractRevisions: []types.FileContractRevision{{
    50  				NewRevisionNumber:    1,
    51  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
    52  				UnlockConditions: types.UnlockConditions{
    53  					PublicKeys: []types.SiaPublicKey{{}, {}},
    54  				},
    55  			}},
    56  		},
    57  	}
    58  
    59  	// Test RecordRootUpdates.
    60  	t.Run("RecordRootUpdates", func(t *testing.T) {
    61  		updateFunc := func(sc *SafeContract) (*unappliedWalTxn, []crypto.Hash, contractHeader, error) {
    62  			revisedHeader := contractHeader{
    63  				Transaction: types.Transaction{
    64  					FileContractRevisions: []types.FileContractRevision{{
    65  						NewRevisionNumber:    2,
    66  						NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
    67  						UnlockConditions: types.UnlockConditions{
    68  							PublicKeys: []types.SiaPublicKey{{}, {}},
    69  						},
    70  					}},
    71  				},
    72  				StorageSpending: types.NewCurrency64(7),
    73  				UploadSpending:  types.NewCurrency64(17),
    74  			}
    75  			revisedRoots := []crypto.Hash{{1}, {2}}
    76  			fcr := revisedHeader.Transaction.FileContractRevisions[0]
    77  			newRoot := revisedRoots[1]
    78  			storageCost := revisedHeader.StorageSpending.Sub(initialHeader.StorageSpending)
    79  			bandwidthCost := revisedHeader.UploadSpending.Sub(initialHeader.UploadSpending)
    80  			txn, err := sc.managedRecordRootUpdates(fcr, map[uint64]rootUpdate{
    81  				uint64(len(revisedRoots) - 1): newRootUpdateAppendRoot(newRoot),
    82  			}, storageCost, bandwidthCost)
    83  			return txn, revisedRoots, revisedHeader, err
    84  		}
    85  		testContractUncomittedTxn(t, initialHeader, updateFunc)
    86  	})
    87  	// Test RecordDownloadIntent.
    88  	t.Run("RecordDownloadIntent", func(t *testing.T) {
    89  		updateFunc := func(sc *SafeContract) (*unappliedWalTxn, []crypto.Hash, contractHeader, error) {
    90  			revisedHeader := contractHeader{
    91  				Transaction: types.Transaction{
    92  					FileContractRevisions: []types.FileContractRevision{{
    93  						NewRevisionNumber:    2,
    94  						NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
    95  						UnlockConditions: types.UnlockConditions{
    96  							PublicKeys: []types.SiaPublicKey{{}, {}},
    97  						},
    98  					}},
    99  				},
   100  				StorageSpending:  types.ZeroCurrency,
   101  				DownloadSpending: types.NewCurrency64(17),
   102  			}
   103  			revisedRoots := []crypto.Hash{{1}}
   104  			fcr := revisedHeader.Transaction.FileContractRevisions[0]
   105  			bandwidthCost := revisedHeader.DownloadSpending.Sub(initialHeader.DownloadSpending)
   106  			txn, err := sc.managedRecordDownloadIntent(fcr, bandwidthCost)
   107  			return txn, revisedRoots, revisedHeader, err
   108  		}
   109  		testContractUncomittedTxn(t, initialHeader, updateFunc)
   110  	})
   111  	// Test RecordClearContractIntent.
   112  	t.Run("RecordClearContractIntent", func(t *testing.T) {
   113  		updateFunc := func(sc *SafeContract) (*unappliedWalTxn, []crypto.Hash, contractHeader, error) {
   114  			revisedHeader := contractHeader{
   115  				Transaction: types.Transaction{
   116  					FileContractRevisions: []types.FileContractRevision{{
   117  						NewRevisionNumber:    2,
   118  						NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   119  						UnlockConditions: types.UnlockConditions{
   120  							PublicKeys: []types.SiaPublicKey{{}, {}},
   121  						},
   122  					}},
   123  				},
   124  				StorageSpending: types.ZeroCurrency,
   125  				UploadSpending:  types.NewCurrency64(17),
   126  			}
   127  			revisedRoots := []crypto.Hash{{1}}
   128  			fcr := revisedHeader.Transaction.FileContractRevisions[0]
   129  			bandwidthCost := revisedHeader.UploadSpending.Sub(initialHeader.UploadSpending)
   130  			txn, err := sc.managedRecordClearContractIntent(fcr, bandwidthCost)
   131  			return txn, revisedRoots, revisedHeader, err
   132  		}
   133  		testContractUncomittedTxn(t, initialHeader, updateFunc)
   134  	})
   135  	// Test RecordPaymentIntent.
   136  	t.Run("RecordPaymentIntent", func(t *testing.T) {
   137  		updateFunc := func(sc *SafeContract) (*unappliedWalTxn, []crypto.Hash, contractHeader, error) {
   138  			revisedHeader := contractHeader{
   139  				Transaction: types.Transaction{
   140  					FileContractRevisions: []types.FileContractRevision{{
   141  						NewRevisionNumber:    2,
   142  						NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   143  						UnlockConditions: types.UnlockConditions{
   144  							PublicKeys: []types.SiaPublicKey{{}, {}},
   145  						},
   146  					}},
   147  				},
   148  				StorageSpending:     types.ZeroCurrency,
   149  				UploadSpending:      types.ZeroCurrency,
   150  				FundAccountSpending: types.NewCurrency64(42),
   151  			}
   152  			revisedRoots := []crypto.Hash{{1}}
   153  			fcr := revisedHeader.Transaction.FileContractRevisions[0]
   154  			amount := revisedHeader.FundAccountSpending
   155  			txn, err := sc.RecordPaymentIntent(fcr, amount, skymodules.SpendingDetails{
   156  				FundAccountSpending: revisedHeader.FundAccountSpending,
   157  			})
   158  			return txn, revisedRoots, revisedHeader, err
   159  		}
   160  		testContractUncomittedTxn(t, initialHeader, updateFunc)
   161  	})
   162  }
   163  
   164  // testContractUncommittedTxn tests that if a contract revision is left in an
   165  // uncommitted state, either version of the contract can be recovered.
   166  func testContractUncomittedTxn(t *testing.T, initialHeader contractHeader, updateFunc func(*SafeContract) (*unappliedWalTxn, []crypto.Hash, contractHeader, error)) {
   167  	if testing.Short() {
   168  		t.SkipNow()
   169  	}
   170  	t.Parallel()
   171  	// create contract set with one contract
   172  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   173  	rl := ratelimit.NewRateLimit(0, 0, 0)
   174  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  	initialRoots := []crypto.Hash{{1}}
   179  	c, err := cs.managedInsertContract(initialHeader, initialRoots)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  
   184  	// apply an update to the contract, but don't commit it
   185  	sc := cs.managedMustAcquire(t, c.ID)
   186  	walTxn, revisedRoots, revisedHeader, err := updateFunc(sc)
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  
   191  	// the state of the contract should match the initial state
   192  	// NOTE: can't use reflect.DeepEqual for the header because it contains
   193  	// types.Currency fields
   194  	merkleRoots, err := sc.merkleRoots.merkleRoots()
   195  	if err != nil {
   196  		t.Fatal("failed to get merkle roots", err)
   197  	}
   198  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(initialHeader)) {
   199  		t.Fatal("contractHeader should match initial contractHeader")
   200  	} else if !reflect.DeepEqual(merkleRoots, initialRoots) {
   201  		t.Fatal("Merkle roots should match initial Merkle roots")
   202  	}
   203  
   204  	// close and reopen the contract set.
   205  	if err := cs.Close(); err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	// the uncommitted transaction should be stored in the contract
   213  	sc = cs.managedMustAcquire(t, c.ID)
   214  	if len(sc.unappliedTxns) != 1 {
   215  		t.Fatal("expected 1 unappliedTxn, got", len(sc.unappliedTxns))
   216  	} else if !bytes.Equal(sc.unappliedTxns[0].Updates[0].Instructions, walTxn.Updates[0].Instructions) {
   217  		t.Fatal("WAL transaction changed")
   218  	}
   219  	// the state of the contract should match the initial state
   220  	merkleRoots, err = sc.merkleRoots.merkleRoots()
   221  	if err != nil {
   222  		t.Fatal("failed to get merkle roots:", err)
   223  	}
   224  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(initialHeader)) {
   225  		t.Fatal("contractHeader should match initial contractHeader", sc.header, initialHeader)
   226  	} else if !reflect.DeepEqual(merkleRoots, initialRoots) {
   227  		t.Fatal("Merkle roots should match initial Merkle roots")
   228  	}
   229  
   230  	// apply the uncommitted transaction
   231  	err = sc.managedCommitTxns()
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	// the uncommitted transaction should be gone now
   236  	if len(sc.unappliedTxns) != 0 {
   237  		t.Fatal("expected 0 unappliedTxns, got", len(sc.unappliedTxns))
   238  	}
   239  	// the state of the contract should now match the revised state
   240  	merkleRoots, err = sc.merkleRoots.merkleRoots()
   241  	if err != nil {
   242  		t.Fatal("failed to get merkle roots:", err)
   243  	}
   244  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(revisedHeader)) {
   245  		t.Fatal("contractHeader should match revised contractHeader", sc.header, revisedHeader)
   246  	} else if !reflect.DeepEqual(merkleRoots, revisedRoots) {
   247  		t.Fatal("Merkle roots should match revised Merkle roots", merkleRoots, revisedRoots)
   248  	}
   249  	// close and reopen the contract set.
   250  	if err := cs.Close(); err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  	// the uncommitted transaction should be gone.
   258  	sc = cs.managedMustAcquire(t, c.ID)
   259  	if len(sc.unappliedTxns) != 0 {
   260  		t.Fatal("expected 0 unappliedTxn, got", len(sc.unappliedTxns))
   261  	}
   262  }
   263  
   264  // TestContractIncompleteWrite tests that if the merkle root section has the wrong
   265  // length due to an incomplete write, it is truncated and the wal transactions
   266  // are applied.
   267  func TestContractIncompleteWrite(t *testing.T) {
   268  	if testing.Short() {
   269  		t.SkipNow()
   270  	}
   271  	t.Parallel()
   272  	// create contract set with one contract
   273  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   274  	rl := ratelimit.NewRateLimit(0, 0, 0)
   275  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	initialHeader := contractHeader{
   280  		Transaction: types.Transaction{
   281  			FileContractRevisions: []types.FileContractRevision{{
   282  				NewRevisionNumber:    1,
   283  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   284  				UnlockConditions: types.UnlockConditions{
   285  					PublicKeys: []types.SiaPublicKey{{}, {}},
   286  				},
   287  			}},
   288  		},
   289  	}
   290  	initialRoots := []crypto.Hash{{1}}
   291  	c, err := cs.managedInsertContract(initialHeader, initialRoots)
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	// apply an update to the contract, but don't commit it
   297  	sc := cs.managedMustAcquire(t, c.ID)
   298  	revisedHeader := contractHeader{
   299  		Transaction: types.Transaction{
   300  			FileContractRevisions: []types.FileContractRevision{{
   301  				NewRevisionNumber:    2,
   302  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   303  				UnlockConditions: types.UnlockConditions{
   304  					PublicKeys: []types.SiaPublicKey{{}, {}},
   305  				},
   306  			}},
   307  		},
   308  		StorageSpending: types.NewCurrency64(7),
   309  		UploadSpending:  types.NewCurrency64(17),
   310  	}
   311  	revisedRoots := []crypto.Hash{{1}, {2}}
   312  	fcr := revisedHeader.Transaction.FileContractRevisions[0]
   313  	newRoot := revisedRoots[1]
   314  	storageCost := revisedHeader.StorageSpending.Sub(initialHeader.StorageSpending)
   315  	bandwidthCost := revisedHeader.UploadSpending.Sub(initialHeader.UploadSpending)
   316  	_, err = sc.managedRecordRootUpdates(fcr, map[uint64]rootUpdate{
   317  		uint64(len(revisedRoots) - 1): newRootUpdateAppendRoot(newRoot),
   318  	}, storageCost, bandwidthCost)
   319  	if err != nil {
   320  		t.Fatal(err)
   321  	}
   322  
   323  	// the state of the contract should match the initial state
   324  	// NOTE: can't use reflect.DeepEqual for the header because it contains
   325  	// types.Currency fields
   326  	merkleRoots, err := sc.merkleRoots.merkleRoots()
   327  	if err != nil {
   328  		t.Fatal("failed to get merkle roots", err)
   329  	}
   330  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(initialHeader)) {
   331  		t.Fatal("contractHeader should match initial contractHeader")
   332  	} else if !reflect.DeepEqual(merkleRoots, initialRoots) {
   333  		t.Fatal("Merkle roots should match initial Merkle roots")
   334  	}
   335  
   336  	// get the size of the merkle roots file.
   337  	size, err := sc.merkleRoots.rootsFile.Size()
   338  	if err != nil {
   339  		t.Fatal(err)
   340  	}
   341  	// the size should be crypto.HashSize since we have exactly one root.
   342  	if size != crypto.HashSize {
   343  		t.Fatal("unexpected merkle root file size", size)
   344  	}
   345  	// truncate the rootsFile to simulate a corruption while writing the second
   346  	// root.
   347  	err = sc.merkleRoots.rootsFile.Truncate(size + crypto.HashSize/2)
   348  	if err != nil {
   349  		t.Fatal(err)
   350  	}
   351  
   352  	// close and reopen the contract set.
   353  	if err := cs.Close(); err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	// the uncommitted txn should be gone.
   361  	sc = cs.managedMustAcquire(t, c.ID)
   362  	if len(sc.unappliedTxns) != 0 {
   363  		t.Fatal("expected 0 unappliedTxn, got", len(sc.unappliedTxns))
   364  	}
   365  	if sc.merkleRoots.len() != 2 {
   366  		t.Fatal("expected 2 roots, got", sc.merkleRoots.len())
   367  	}
   368  	cs.Return(sc)
   369  	cs.Close()
   370  }
   371  
   372  // TestContractLargeHeader tests if adding or modifying a contract with a large
   373  // header works as expected.
   374  func TestContractLargeHeader(t *testing.T) {
   375  	if testing.Short() {
   376  		t.SkipNow()
   377  	}
   378  	t.Parallel()
   379  	// create contract set with one contract
   380  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   381  	rl := ratelimit.NewRateLimit(0, 0, 0)
   382  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	largeHeader := contractHeader{
   387  		Transaction: types.Transaction{
   388  			ArbitraryData: [][]byte{fastrand.Bytes(1 << 20 * 5)}, // excessive 5 MiB Transaction
   389  			FileContractRevisions: []types.FileContractRevision{{
   390  				NewRevisionNumber:    1,
   391  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   392  				UnlockConditions: types.UnlockConditions{
   393  					PublicKeys: []types.SiaPublicKey{{}, {}},
   394  				},
   395  			}},
   396  		},
   397  	}
   398  	initialRoots := []crypto.Hash{{1}}
   399  	// Inserting a contract with a large header should work.
   400  	c, err := cs.managedInsertContract(largeHeader, initialRoots)
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  
   405  	sc, ok := cs.Acquire(c.ID)
   406  	if !ok {
   407  		t.Fatal("failed to acquire contract")
   408  	}
   409  	// Applying a large header update should also work.
   410  	if err := sc.applySetHeader(largeHeader); err != nil {
   411  		t.Fatal(err)
   412  	}
   413  }
   414  
   415  // TestContractSetInsert checks if inserting contracts into the set is ACID.
   416  func TestContractSetInsertInterrupted(t *testing.T) {
   417  	if testing.Short() {
   418  		t.SkipNow()
   419  	}
   420  	t.Parallel()
   421  	// create contract set with a custom dependency.
   422  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   423  	rl := ratelimit.NewRateLimit(0, 0, 0)
   424  	cs, err := NewContractSet(dir, rl, &dependencyInterruptContractInsertion{})
   425  	if err != nil {
   426  		t.Fatal(err)
   427  	}
   428  	contractHeader := contractHeader{
   429  		Transaction: types.Transaction{
   430  			FileContractRevisions: []types.FileContractRevision{{
   431  				NewRevisionNumber:    1,
   432  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   433  				UnlockConditions: types.UnlockConditions{
   434  					PublicKeys: []types.SiaPublicKey{{}, {}},
   435  				},
   436  			}},
   437  		},
   438  	}
   439  	initialRoots := []crypto.Hash{{1}}
   440  	// Inserting the contract should fail due to the dependency.
   441  	c, err := cs.managedInsertContract(contractHeader, initialRoots)
   442  	if err == nil || !strings.Contains(err.Error(), "interrupted") {
   443  		t.Fatal("insertion should have been interrupted")
   444  	}
   445  
   446  	// Reload the contract set. The contract should be there.
   447  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   448  	if err != nil {
   449  		t.Fatal(err)
   450  	}
   451  	sc, ok := cs.Acquire(c.ID)
   452  	if !ok {
   453  		t.Fatal("faild to acquire contract")
   454  	}
   455  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(contractHeader)) {
   456  		t.Log(sc.header)
   457  		t.Log(contractHeader)
   458  		t.Error("header doesn't match")
   459  	}
   460  	mr, err := sc.merkleRoots.merkleRoots()
   461  	if err != nil {
   462  		t.Fatal(err)
   463  	}
   464  	if !reflect.DeepEqual(mr, initialRoots) {
   465  		t.Error("roots don't match")
   466  	}
   467  }
   468  
   469  // TestContractCommitAndRecordPaymentIntent verifies the functionality of the
   470  // RecordPaymentIntent and CommitPaymentIntent methods on the SafeContract
   471  func TestContractRecordAndCommitPaymentIntent(t *testing.T) {
   472  	if testing.Short() {
   473  		t.SkipNow()
   474  	}
   475  	t.Parallel()
   476  
   477  	blockHeight := types.BlockHeight(fastrand.Intn(100))
   478  
   479  	// create contract set
   480  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   481  	rl := ratelimit.NewRateLimit(0, 0, 0)
   482  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  
   487  	// add a contract
   488  	initialHeader := contractHeader{
   489  		Transaction: types.Transaction{
   490  			FileContractRevisions: []types.FileContractRevision{{
   491  				NewRevisionNumber: 1,
   492  				NewValidProofOutputs: []types.SiacoinOutput{
   493  					{Value: types.SiacoinPrecision},
   494  					{Value: types.ZeroCurrency},
   495  				},
   496  				NewMissedProofOutputs: []types.SiacoinOutput{
   497  					{Value: types.SiacoinPrecision},
   498  					{Value: types.ZeroCurrency},
   499  					{Value: types.ZeroCurrency},
   500  				},
   501  				UnlockConditions: types.UnlockConditions{
   502  					PublicKeys: []types.SiaPublicKey{{}, {}},
   503  				},
   504  			}},
   505  		},
   506  	}
   507  	initialRoots := []crypto.Hash{{1}}
   508  	contract, err := cs.managedInsertContract(initialHeader, initialRoots)
   509  	if err != nil {
   510  		t.Fatal(err)
   511  	}
   512  	sc := cs.managedMustAcquire(t, contract.ID)
   513  
   514  	// create a helper function that records the intent, creates the transaction
   515  	// containing the given revision and then commits the intent depending on
   516  	// whether the given flag was set to true
   517  	processTxnWithRevision := func(rev types.FileContractRevision, amount types.Currency, details skymodules.SpendingDetails, commit bool) {
   518  		// record the payment intent
   519  		walTxn, err := sc.RecordPaymentIntent(rev, amount, details)
   520  		if err != nil {
   521  			t.Fatal("Failed to record payment intent")
   522  		}
   523  
   524  		// create transaction containing the revision
   525  		signedTxn := rev.ToTransaction()
   526  		sig := sc.Sign(signedTxn.SigHash(0, blockHeight))
   527  		signedTxn.TransactionSignatures[0].Signature = sig[:]
   528  
   529  		// only commit the intent if the flag is true
   530  		if !commit {
   531  			return
   532  		}
   533  		err = sc.CommitPaymentIntent(walTxn, signedTxn, amount, details)
   534  		if err != nil {
   535  			t.Fatal("Failed to commit payment intent")
   536  		}
   537  	}
   538  
   539  	// create a payment revision for a FundAccount RPC
   540  	curr := sc.LastRevision()
   541  	amount := types.NewCurrency64(10)
   542  	rpcCost := types.NewCurrency64(1)
   543  	rev, err := curr.PaymentRevision(amount.Add(rpcCost))
   544  	if err != nil {
   545  		t.Fatal(err)
   546  	}
   547  	processTxnWithRevision(rev, amount, skymodules.SpendingDetails{
   548  		FundAccountSpending: amount,
   549  		MaintenanceSpending: skymodules.MaintenanceSpending{FundAccountCost: rpcCost},
   550  	}, true)
   551  
   552  	// create another payment revision, this time for an MDM RPC
   553  	curr = sc.LastRevision()
   554  	amount = types.NewCurrency64(20)
   555  	rpcCost = types.ZeroCurrency
   556  	rev, err = curr.PaymentRevision(amount.Add(rpcCost))
   557  	if err != nil {
   558  		t.Fatal(err)
   559  	}
   560  	processTxnWithRevision(rev, amount, skymodules.SpendingDetails{}, true)
   561  
   562  	// create another payment revision, this time for a PT update RPC
   563  	curr = sc.LastRevision()
   564  	amount = types.NewCurrency64(3)
   565  	rpcCost = types.NewCurrency64(3)
   566  	rev, err = curr.PaymentRevision(amount.Add(rpcCost))
   567  	if err != nil {
   568  		t.Fatal(err)
   569  	}
   570  	processTxnWithRevision(rev, amount, skymodules.SpendingDetails{
   571  		MaintenanceSpending: skymodules.MaintenanceSpending{UpdatePriceTableCost: rpcCost},
   572  	}, true)
   573  	expectedRevNumber := rev.NewRevisionNumber
   574  
   575  	// create another payment revision, for an account balance sync,
   576  	// but this time we don't commit it
   577  	curr = sc.LastRevision()
   578  	amount = types.NewCurrency64(4)
   579  	rpcCost = types.NewCurrency64(4)
   580  	rev, err = curr.PaymentRevision(amount.Add(rpcCost))
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  	processTxnWithRevision(rev, amount, skymodules.SpendingDetails{
   585  		MaintenanceSpending: skymodules.MaintenanceSpending{AccountBalanceCost: rpcCost},
   586  	}, false)
   587  
   588  	// reload the contract set
   589  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   590  	if err != nil {
   591  		t.Fatal(err)
   592  	}
   593  	sc = cs.managedMustAcquire(t, contract.ID)
   594  
   595  	if sc.LastRevision().NewRevisionNumber != expectedRevNumber {
   596  		t.Fatal("Unexpected revision number after reloading the contract set")
   597  	}
   598  
   599  	// we expect the `FundAccount` spending metric to reflect exactly the amount
   600  	// of money that should have made it into the EA
   601  	expectedFundAccountSpending := types.NewCurrency64(10)
   602  	if !sc.header.FundAccountSpending.Equals(expectedFundAccountSpending) {
   603  		t.Fatal("unexpected", sc.header.FundAccountSpending)
   604  	}
   605  
   606  	// we expect the `Maintenance` spending metric to reflect the sum of the rpc
   607  	// cost for the fund account, and the amount spent on updating the price
   608  	// table. This means that the cost of the MDM RPC and the non committed
   609  	// account balance sync should not be included
   610  	expectedMaintenanceSpending := types.NewCurrency64(1).Add(types.NewCurrency64(3))
   611  	if !sc.header.MaintenanceSpending.Sum().Equals(expectedMaintenanceSpending) {
   612  		t.Fatal("unexpected", sc.header.MaintenanceSpending)
   613  	}
   614  }
   615  
   616  // TestContractRefCounter checks if refCounter behaves as expected when called
   617  // from Contract
   618  func TestContractRefCounter(t *testing.T) {
   619  	if testing.Short() {
   620  		t.SkipNow()
   621  	}
   622  	t.Parallel()
   623  
   624  	// create a contract set
   625  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   626  	rl := ratelimit.NewRateLimit(0, 0, 0)
   627  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   628  	if err != nil {
   629  		t.Fatal(err)
   630  	}
   631  	// add a contract
   632  	initialHeader := contractHeader{
   633  		Transaction: types.Transaction{
   634  			FileContractRevisions: []types.FileContractRevision{{
   635  				NewRevisionNumber:    1,
   636  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   637  				UnlockConditions: types.UnlockConditions{
   638  					PublicKeys: []types.SiaPublicKey{{}, {}},
   639  				},
   640  			}},
   641  		},
   642  	}
   643  	initialRoots := []crypto.Hash{{1}}
   644  	c, err := cs.managedInsertContract(initialHeader, initialRoots)
   645  	if err != nil {
   646  		t.Fatal(err)
   647  	}
   648  	sc := cs.managedMustAcquire(t, c.ID)
   649  	// verify that the refcounter exists and has the correct size
   650  	if sc.staticRC == nil {
   651  		t.Fatal("refCounter was not created with the contract.")
   652  	}
   653  	if sc.staticRC.numSectors != uint64(sc.merkleRoots.numMerkleRoots) {
   654  		t.Fatalf("refCounter has wrong number of sectors. Expected %d, found %d", uint64(sc.merkleRoots.numMerkleRoots), sc.staticRC.numSectors)
   655  	}
   656  	fi, err := os.Stat(sc.staticRC.filepath)
   657  	if err != nil {
   658  		t.Fatal("Failed to read refcounter file from disk:", err)
   659  	}
   660  	rcFileSize := refCounterHeaderSize + int64(sc.merkleRoots.numMerkleRoots)*2
   661  	if fi.Size() != rcFileSize {
   662  		t.Fatalf("refCounter file on disk has wrong size. Expected %d, got %d", rcFileSize, fi.Size())
   663  	}
   664  
   665  	// upload a new sector
   666  	txn := types.Transaction{
   667  		FileContractRevisions: []types.FileContractRevision{{
   668  			NewRevisionNumber:    2,
   669  			NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   670  			UnlockConditions: types.UnlockConditions{
   671  				PublicKeys: []types.SiaPublicKey{{}, {}},
   672  			},
   673  		}},
   674  	}
   675  	revisedHeader := contractHeader{
   676  		Transaction:     txn,
   677  		StorageSpending: types.NewCurrency64(7),
   678  		UploadSpending:  types.NewCurrency64(17),
   679  	}
   680  	newRev := revisedHeader.Transaction.FileContractRevisions[0]
   681  	newRoot := crypto.Hash{2}
   682  	storageCost := revisedHeader.StorageSpending.Sub(initialHeader.StorageSpending)
   683  	bandwidthCost := revisedHeader.UploadSpending.Sub(initialHeader.UploadSpending)
   684  	walTxn, err := sc.managedRecordRootUpdates(newRev, map[uint64]rootUpdate{
   685  		uint64(len(initialRoots)): newRootUpdateAppendRoot(newRoot),
   686  	}, storageCost, bandwidthCost)
   687  	if err != nil {
   688  		t.Fatal(err)
   689  	}
   690  	// sign the transaction
   691  	txn.TransactionSignatures = []types.TransactionSignature{
   692  		{
   693  			ParentID:       crypto.Hash(newRev.ParentID),
   694  			CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   695  			PublicKeyIndex: 0, // renter key is always first -- see formContract
   696  		},
   697  		{
   698  			ParentID:       crypto.Hash(newRev.ParentID),
   699  			PublicKeyIndex: 1,
   700  			CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   701  			Signature:      nil, // to be provided by host
   702  		},
   703  	}
   704  	// commit the change
   705  	err = sc.managedCommitAppend(walTxn, txn, storageCost, bandwidthCost)
   706  	if err != nil {
   707  		t.Fatal(err)
   708  	}
   709  	// verify that the refcounter increased with 1, as expected
   710  	if sc.staticRC.numSectors != uint64(sc.merkleRoots.numMerkleRoots) {
   711  		t.Fatalf("refCounter has wrong number of sectors. Expected %d, found %d", uint64(sc.merkleRoots.numMerkleRoots), sc.staticRC.numSectors)
   712  	}
   713  	fi, err = os.Stat(sc.staticRC.filepath)
   714  	if err != nil {
   715  		t.Fatal("Failed to read refcounter file from disk:", err)
   716  	}
   717  	rcFileSize = refCounterHeaderSize + int64(sc.merkleRoots.numMerkleRoots)*2
   718  	if fi.Size() != rcFileSize {
   719  		t.Fatalf("refCounter file on disk has wrong size. Expected %d, got %d", rcFileSize, fi.Size())
   720  	}
   721  }
   722  
   723  // TestContractRecordCommitDownloadIntent tests recording and committing
   724  // downloads and makes sure they use the wal correctly.
   725  func TestContractRecordCommitDownloadIntent(t *testing.T) {
   726  	if testing.Short() {
   727  		t.SkipNow()
   728  	}
   729  	t.Parallel()
   730  
   731  	blockHeight := types.BlockHeight(fastrand.Intn(100))
   732  
   733  	// create contract set
   734  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   735  	rl := ratelimit.NewRateLimit(0, 0, 0)
   736  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   737  	if err != nil {
   738  		t.Fatal(err)
   739  	}
   740  
   741  	// add a contract
   742  	initialHeader := contractHeader{
   743  		Transaction: types.Transaction{
   744  			FileContractRevisions: []types.FileContractRevision{{
   745  				NewRevisionNumber: 1,
   746  				NewValidProofOutputs: []types.SiacoinOutput{
   747  					{Value: types.SiacoinPrecision},
   748  					{Value: types.ZeroCurrency},
   749  				},
   750  				NewMissedProofOutputs: []types.SiacoinOutput{
   751  					{Value: types.SiacoinPrecision},
   752  					{Value: types.ZeroCurrency},
   753  					{Value: types.ZeroCurrency},
   754  				},
   755  				UnlockConditions: types.UnlockConditions{
   756  					PublicKeys: []types.SiaPublicKey{{}, {}},
   757  				},
   758  			}},
   759  		},
   760  	}
   761  	initialRoots := []crypto.Hash{{1}}
   762  	contract, err := cs.managedInsertContract(initialHeader, initialRoots)
   763  	if err != nil {
   764  		t.Fatal(err)
   765  	}
   766  	sc := cs.managedMustAcquire(t, contract.ID)
   767  
   768  	// create a download revision
   769  	curr := sc.LastRevision()
   770  	amount := types.NewCurrency64(fastrand.Uint64n(100))
   771  	rev, err := newDownloadRevision(curr, amount)
   772  	if err != nil {
   773  		t.Fatal(err)
   774  	}
   775  
   776  	// record the download intent
   777  	walTxn, err := sc.managedRecordDownloadIntent(rev, amount)
   778  	if err != nil {
   779  		t.Fatal("Failed to record payment intent")
   780  	}
   781  	if len(sc.unappliedTxns) != 1 {
   782  		t.Fatalf("expected %v unapplied txns but got %v", 1, len(sc.unappliedTxns))
   783  	}
   784  
   785  	// create transaction containing the revision
   786  	signedTxn := rev.ToTransaction()
   787  	sig := sc.Sign(signedTxn.SigHash(0, blockHeight))
   788  	signedTxn.TransactionSignatures[0].Signature = sig[:]
   789  
   790  	// don't commit the download. Instead simulate a crash by reloading the
   791  	// contract set.
   792  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   793  	if err != nil {
   794  		t.Fatal(err)
   795  	}
   796  	sc = cs.managedMustAcquire(t, contract.ID)
   797  
   798  	if sc.LastRevision().NewRevisionNumber != curr.NewRevisionNumber {
   799  		t.Fatal("Unexpected revision number after reloading the contract set")
   800  	}
   801  	if len(sc.unappliedTxns) != 1 {
   802  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
   803  	}
   804  
   805  	// start a new download
   806  	walTxn, err = sc.managedRecordDownloadIntent(rev, amount)
   807  	if err != nil {
   808  		t.Fatal("Failed to record payment intent")
   809  	}
   810  	if len(sc.unappliedTxns) != 2 {
   811  		t.Fatalf("expected %v unapplied txns but got %v", 2, len(sc.unappliedTxns))
   812  	}
   813  
   814  	// commit the download. This should remove all unapplied txns.
   815  	err = sc.managedCommitDownload(walTxn, signedTxn, amount)
   816  	if err != nil {
   817  		t.Fatal("Failed to commit payment intent")
   818  	}
   819  	if len(sc.unappliedTxns) != 0 {
   820  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
   821  	}
   822  
   823  	// restart again. We still expect 0 unapplied txns.
   824  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   825  	if err != nil {
   826  		t.Fatal(err)
   827  	}
   828  	sc = cs.managedMustAcquire(t, contract.ID)
   829  
   830  	if sc.LastRevision().NewRevisionNumber != rev.NewRevisionNumber {
   831  		t.Fatal("Unexpected revision number after reloading the contract set")
   832  	}
   833  	if len(sc.unappliedTxns) != 0 {
   834  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
   835  	}
   836  }
   837  
   838  // TestContractRecordCommitAppendIntent tests recording and committing
   839  // downloads and makes sure they use the wal correctly.
   840  func TestContractRecordCommitAppendIntent(t *testing.T) {
   841  	if testing.Short() {
   842  		t.SkipNow()
   843  	}
   844  	t.Parallel()
   845  
   846  	blockHeight := types.BlockHeight(fastrand.Intn(100))
   847  
   848  	// create contract set
   849  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   850  	rl := ratelimit.NewRateLimit(0, 0, 0)
   851  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   852  	if err != nil {
   853  		t.Fatal(err)
   854  	}
   855  
   856  	// add a contract
   857  	initialHeader := contractHeader{
   858  		Transaction: types.Transaction{
   859  			FileContractRevisions: []types.FileContractRevision{{
   860  				NewRevisionNumber: 1,
   861  				NewValidProofOutputs: []types.SiacoinOutput{
   862  					{Value: types.SiacoinPrecision},
   863  					{Value: types.SiacoinPrecision},
   864  				},
   865  				NewMissedProofOutputs: []types.SiacoinOutput{
   866  					{Value: types.SiacoinPrecision},
   867  					{Value: types.SiacoinPrecision},
   868  					{Value: types.ZeroCurrency},
   869  				},
   870  				UnlockConditions: types.UnlockConditions{
   871  					PublicKeys: []types.SiaPublicKey{{}, {}},
   872  				},
   873  			}},
   874  		},
   875  	}
   876  	initialRoots := []crypto.Hash{{1}}
   877  	contract, err := cs.managedInsertContract(initialHeader, initialRoots)
   878  	if err != nil {
   879  		t.Fatal(err)
   880  	}
   881  	sc := cs.managedMustAcquire(t, contract.ID)
   882  
   883  	// create a append revision
   884  	curr := sc.LastRevision()
   885  	bandwidth := types.NewCurrency64(fastrand.Uint64n(100))
   886  	collateral := types.NewCurrency64(fastrand.Uint64n(100))
   887  	storage := types.NewCurrency64(fastrand.Uint64n(100))
   888  	newRoot := crypto.Hash{1}
   889  	rev, err := newUploadRevision(curr, newRoot, bandwidth.Add(storage), collateral)
   890  	if err != nil {
   891  		t.Fatal(err)
   892  	}
   893  
   894  	// record the append intent
   895  	walTxn, err := sc.managedRecordRootUpdates(rev, map[uint64]rootUpdate{
   896  		uint64(len(initialRoots)): newRootUpdateAppendRoot(newRoot),
   897  	}, storage, bandwidth)
   898  	if err != nil {
   899  		t.Fatal("Failed to record payment intent")
   900  	}
   901  	if len(sc.unappliedTxns) != 1 {
   902  		t.Fatalf("expected %v unapplied txns but got %v", 1, len(sc.unappliedTxns))
   903  	}
   904  
   905  	// create transaction containing the revision
   906  	signedTxn := rev.ToTransaction()
   907  	sig := sc.Sign(signedTxn.SigHash(0, blockHeight))
   908  	signedTxn.TransactionSignatures[0].Signature = sig[:]
   909  
   910  	// don't commit the download. Instead simulate a crash by reloading the
   911  	// contract set.
   912  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   913  	if err != nil {
   914  		t.Fatal(err)
   915  	}
   916  	sc = cs.managedMustAcquire(t, contract.ID)
   917  
   918  	if sc.LastRevision().NewRevisionNumber != curr.NewRevisionNumber {
   919  		t.Fatal("Unexpected revision number after reloading the contract set")
   920  	}
   921  	if len(sc.unappliedTxns) != 1 {
   922  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
   923  	}
   924  
   925  	// start a new append
   926  	walTxn, err = sc.managedRecordRootUpdates(rev, map[uint64]rootUpdate{
   927  		uint64(len(initialRoots) + 1): newRootUpdateAppendRoot(newRoot),
   928  	}, storage, bandwidth)
   929  	if err != nil {
   930  		t.Fatal("Failed to record payment intent")
   931  	}
   932  	if len(sc.unappliedTxns) != 2 {
   933  		t.Fatalf("expected %v unapplied txns but got %v", 2, len(sc.unappliedTxns))
   934  	}
   935  
   936  	// commit the append. This should remove all unapplied txns.
   937  	err = sc.managedCommitAppend(walTxn, signedTxn, storage, bandwidth)
   938  	if err != nil {
   939  		t.Fatal("Failed to commit payment intent")
   940  	}
   941  	if len(sc.unappliedTxns) != 0 {
   942  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
   943  	}
   944  
   945  	// restart again. We still expect 0 unapplied txns.
   946  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
   947  	if err != nil {
   948  		t.Fatal(err)
   949  	}
   950  	sc = cs.managedMustAcquire(t, contract.ID)
   951  
   952  	if sc.LastRevision().NewRevisionNumber != rev.NewRevisionNumber {
   953  		t.Fatal("Unexpected revision number after reloading the contract set")
   954  	}
   955  	if len(sc.unappliedTxns) != 0 {
   956  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
   957  	}
   958  }
   959  
   960  // TestContractRecordCommitRenewAndClearIntent tests recording and committing
   961  // downloads and makes sure they use the wal correctly.
   962  func TestContractRecordCommitRenewAndClearIntent(t *testing.T) {
   963  	if testing.Short() {
   964  		t.SkipNow()
   965  	}
   966  	t.Parallel()
   967  
   968  	blockHeight := types.BlockHeight(fastrand.Intn(100))
   969  
   970  	// create contract set
   971  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   972  	rl := ratelimit.NewRateLimit(0, 0, 0)
   973  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
   974  	if err != nil {
   975  		t.Fatal(err)
   976  	}
   977  
   978  	// add a contract
   979  	initialHeader := contractHeader{
   980  		Transaction: types.Transaction{
   981  			FileContractRevisions: []types.FileContractRevision{{
   982  				NewRevisionNumber: 1,
   983  				NewValidProofOutputs: []types.SiacoinOutput{
   984  					{Value: types.SiacoinPrecision},
   985  					{Value: types.ZeroCurrency},
   986  				},
   987  				NewMissedProofOutputs: []types.SiacoinOutput{
   988  					{Value: types.SiacoinPrecision},
   989  					{Value: types.ZeroCurrency},
   990  					{Value: types.ZeroCurrency},
   991  				},
   992  				UnlockConditions: types.UnlockConditions{
   993  					PublicKeys: []types.SiaPublicKey{{}, {}},
   994  				},
   995  			}},
   996  		},
   997  	}
   998  	initialRoots := []crypto.Hash{{1}}
   999  	contract, err := cs.managedInsertContract(initialHeader, initialRoots)
  1000  	if err != nil {
  1001  		t.Fatal(err)
  1002  	}
  1003  	sc := cs.managedMustAcquire(t, contract.ID)
  1004  
  1005  	// create a renew revision. It's the same as a payment revision with small
  1006  	// differences.
  1007  	bandwidth := types.NewCurrency64(fastrand.Uint64n(100))
  1008  	curr := sc.LastRevision()
  1009  	rev, err := curr.PaymentRevision(bandwidth)
  1010  	if err != nil {
  1011  		t.Fatal(err)
  1012  	}
  1013  	rev.NewFileSize = 0
  1014  	rev.NewFileSize = 0
  1015  	rev.NewFileMerkleRoot = crypto.Hash{}
  1016  	rev.NewRevisionNumber = math.MaxUint64
  1017  	rev.NewMissedProofOutputs = rev.NewValidProofOutputs
  1018  
  1019  	// record the clear contract intent
  1020  	walTxn, err := sc.managedRecordClearContractIntent(rev, bandwidth)
  1021  	if err != nil {
  1022  		t.Fatal("Failed to record payment intent")
  1023  	}
  1024  	if len(sc.unappliedTxns) != 1 {
  1025  		t.Fatalf("expected %v unapplied txns but got %v", 1, len(sc.unappliedTxns))
  1026  	}
  1027  
  1028  	// create transaction containing the revision
  1029  	signedTxn := rev.ToTransaction()
  1030  	sig := sc.Sign(signedTxn.SigHash(0, blockHeight))
  1031  	signedTxn.TransactionSignatures[0].Signature = sig[:]
  1032  
  1033  	// don't commit the download. Instead simulate a crash by reloading the
  1034  	// contract set.
  1035  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
  1036  	if err != nil {
  1037  		t.Fatal(err)
  1038  	}
  1039  	sc = cs.managedMustAcquire(t, contract.ID)
  1040  
  1041  	if sc.LastRevision().NewRevisionNumber != curr.NewRevisionNumber {
  1042  		t.Fatal("Unexpected revision number after reloading the contract set")
  1043  	}
  1044  	if len(sc.unappliedTxns) != 1 {
  1045  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
  1046  	}
  1047  
  1048  	// start a new download
  1049  	walTxn, err = sc.managedRecordClearContractIntent(rev, bandwidth)
  1050  	if err != nil {
  1051  		t.Fatal("Failed to record payment intent")
  1052  	}
  1053  	if len(sc.unappliedTxns) != 2 {
  1054  		t.Fatalf("expected %v unapplied txns but got %v", 2, len(sc.unappliedTxns))
  1055  	}
  1056  
  1057  	// commit the download. This should remove all unapplied txns.
  1058  	err = sc.managedCommitClearContract(walTxn, signedTxn, bandwidth)
  1059  	if err != nil {
  1060  		t.Fatal("Failed to commit payment intent")
  1061  	}
  1062  	if len(sc.unappliedTxns) != 0 {
  1063  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
  1064  	}
  1065  
  1066  	// restart again. We still expect 0 unapplied txns.
  1067  	cs, err = NewContractSet(dir, rl, modules.ProdDependencies)
  1068  	if err != nil {
  1069  		t.Fatal(err)
  1070  	}
  1071  	sc = cs.managedMustAcquire(t, contract.ID)
  1072  
  1073  	if sc.LastRevision().NewRevisionNumber != rev.NewRevisionNumber {
  1074  		t.Fatal("Unexpected revision number after reloading the contract set")
  1075  	}
  1076  	if len(sc.unappliedTxns) != 0 {
  1077  		t.Fatalf("expected %v unapplied txns but got %v", 0, len(sc.unappliedTxns))
  1078  	}
  1079  	if sc.Utility().GoodForRenew {
  1080  		t.Fatal("contract shouldn't be good for renew")
  1081  	}
  1082  	if sc.Utility().GoodForUpload {
  1083  		t.Fatal("contract shouldn't be good for upload")
  1084  	}
  1085  	if !sc.Utility().Locked {
  1086  		t.Fatal("contract should be locked")
  1087  	}
  1088  }
  1089  
  1090  // TestPanicOnOverwritingNewerRevision tests if attempting to
  1091  // overwrite a contract header with an old revision triggers a panic.
  1092  func TestPanicOnOverwritingNewerRevision(t *testing.T) {
  1093  	if testing.Short() {
  1094  		t.SkipNow()
  1095  	}
  1096  	t.Parallel()
  1097  	// create contract set with one contract
  1098  	dir := build.TempDir(filepath.Join("proto", t.Name()))
  1099  	rl := ratelimit.NewRateLimit(0, 0, 0)
  1100  	cs, err := NewContractSet(dir, rl, modules.ProdDependencies)
  1101  	if err != nil {
  1102  		t.Fatal(err)
  1103  	}
  1104  	header := contractHeader{
  1105  		Transaction: types.Transaction{
  1106  			FileContractRevisions: []types.FileContractRevision{{
  1107  				NewRevisionNumber:    2,
  1108  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
  1109  				UnlockConditions: types.UnlockConditions{
  1110  					PublicKeys: []types.SiaPublicKey{{}, {}},
  1111  				},
  1112  			}},
  1113  		},
  1114  	}
  1115  	initialRoots := []crypto.Hash{{1}}
  1116  	c, err := cs.managedInsertContract(header, initialRoots)
  1117  	if err != nil {
  1118  		t.Fatal(err)
  1119  	}
  1120  
  1121  	sc, ok := cs.Acquire(c.ID)
  1122  	if !ok {
  1123  		t.Fatal("failed to acquire contract")
  1124  	}
  1125  	// Trying to set a header with an older revision should trigger a panic.
  1126  	header.Transaction.FileContractRevisions[0].NewRevisionNumber = 1
  1127  	defer func() {
  1128  		if r := recover(); r == nil {
  1129  			t.Fatalf("expected panic when attempting to overwrite a newer contract header revision")
  1130  		}
  1131  	}()
  1132  	if err := sc.applySetHeader(header); err != nil {
  1133  		t.Fatal(err)
  1134  	}
  1135  }