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

     1  package host
     2  
     3  // storageobligations_smoke_test.go performs smoke testing on the the storage
     4  // obligation management. This includes adding valid storage obligations, and
     5  // waiting until they expire, to see if the failure modes are all handled
     6  // correctly.
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/NebulousLabs/Sia/crypto"
    12  	"github.com/NebulousLabs/Sia/modules"
    13  	"github.com/NebulousLabs/Sia/types"
    14  
    15  	"github.com/NebulousLabs/bolt"
    16  )
    17  
    18  // randSector creates a random sector, returning the sector along with the
    19  // Merkle root of the sector.
    20  func randSector() (crypto.Hash, []byte, error) {
    21  	sectorData, err := crypto.RandBytes(int(modules.SectorSize))
    22  	if err != nil {
    23  		return crypto.Hash{}, nil, err
    24  	}
    25  	sectorRoot := crypto.MerkleRoot(sectorData)
    26  	return sectorRoot, sectorData, nil
    27  }
    28  
    29  // newTesterStorageObligation uses the wallet to create and fund a file
    30  // contract that will form the foundation of a storage obligation.
    31  func (ht *hostTester) newTesterStorageObligation() (*storageObligation, error) {
    32  	// Create the file contract that will be used in the obligation.
    33  	builder := ht.wallet.StartTransaction()
    34  	// Fund the file contract with a payout. The payout needs to be big enough
    35  	// that the expected revenue is larger than the fee that the host may end
    36  	// up paying.
    37  	payout := types.SiacoinPrecision.Mul64(1e3)
    38  	err := builder.FundSiacoins(payout)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	// Add the file contract that consumes the funds.
    43  	_ = builder.AddFileContract(types.FileContract{
    44  		// Because this file contract needs to be able to accept file contract
    45  		// revisions, the expiration is put more than
    46  		// 'revisionSubmissionBuffer' blocks into the future.
    47  		WindowStart: ht.host.blockHeight + revisionSubmissionBuffer + 2,
    48  		WindowEnd:   ht.host.blockHeight + revisionSubmissionBuffer + defaultWindowSize + 2,
    49  
    50  		Payout: payout,
    51  		ValidProofOutputs: []types.SiacoinOutput{
    52  			{
    53  				Value: types.PostTax(ht.host.blockHeight, payout),
    54  			},
    55  			{
    56  				Value: types.ZeroCurrency,
    57  			},
    58  		},
    59  		MissedProofOutputs: []types.SiacoinOutput{
    60  			{
    61  				Value: types.PostTax(ht.host.blockHeight, payout),
    62  			},
    63  			{
    64  				Value: types.ZeroCurrency,
    65  			},
    66  		},
    67  		UnlockHash:     (types.UnlockConditions{}).UnlockHash(),
    68  		RevisionNumber: 0,
    69  	})
    70  	// Sign the transaction.
    71  	tSet, err := builder.Sign(true)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	// Assemble and return the storage obligation.
    77  	so := &storageObligation{
    78  		OriginTransactionSet: tSet,
    79  
    80  		// TODO: There are no tracking values, because no fees were added.
    81  	}
    82  	return so, nil
    83  }
    84  
    85  // TestBlankStorageObligation checks that the host correctly manages a blank
    86  // storage obligation.
    87  func TestBlankStorageObligation(t *testing.T) {
    88  	if testing.Short() {
    89  		t.SkipNow()
    90  	}
    91  	t.Parallel()
    92  	ht, err := newHostTester("TestBlankStorageObligation")
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	// Start by adding a storage obligation to the host. To emulate conditions
    98  	// of a renter creating the first contract, the storage obligation has no
    99  	// data, but does have money.
   100  	so, err := ht.newTesterStorageObligation()
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  	err = ht.host.lockStorageObligation(so)
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  	err = ht.host.addStorageObligation(so)
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	err = ht.host.unlockStorageObligation(so)
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	// Storage obligation should not be marked as having the transaction
   117  	// confirmed on the blockchain.
   118  	if so.OriginConfirmed {
   119  		t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way")
   120  	}
   121  
   122  	// Mine a block to confirm the transaction containing the storage
   123  	// obligation.
   124  	_, err = ht.miner.AddBlock()
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	// Load the storage obligation from the database, see if it updated
   129  	// correctly.
   130  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   131  		*so, err = getStorageObligation(tx, so.id())
   132  		if err != nil {
   133  			return err
   134  		}
   135  		return nil
   136  	})
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	if !so.OriginConfirmed {
   141  		t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined")
   142  	}
   143  
   144  	// Mine until the host would be submitting a storage proof. Check that the
   145  	// host has cleared out the storage proof - the consensus code makes it
   146  	// impossible to submit a storage proof for an empty file contract, so the
   147  	// host should fail and give up by deleting the storage obligation.
   148  	for i := types.BlockHeight(0); i <= revisionSubmissionBuffer*2+1; i++ {
   149  		_, err := ht.miner.AddBlock()
   150  		if err != nil {
   151  			t.Fatal(err)
   152  		}
   153  	}
   154  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   155  		*so, err = getStorageObligation(tx, so.id())
   156  		if err != nil {
   157  			return err
   158  		}
   159  		return nil
   160  	})
   161  	if err != errNoStorageObligation {
   162  		t.Fatal(err)
   163  	}
   164  }
   165  
   166  // TestSingleSectorObligationStack checks that the host correctly manages a
   167  // storage obligation with a single sector, the revision is created the same
   168  // block as the file contract.
   169  func TestSingleSectorStorageObligationStack(t *testing.T) {
   170  	if testing.Short() {
   171  		t.SkipNow()
   172  	}
   173  	t.Parallel()
   174  	ht, err := newHostTester("TestSingleSectorStorageObligationStack")
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  
   179  	// Start by adding a storage obligation to the host. To emulate conditions
   180  	// of a renter creating the first contract, the storage obligation has no
   181  	// data, but does have money.
   182  	so, err := ht.newTesterStorageObligation()
   183  	if err != nil {
   184  		t.Fatal(err)
   185  	}
   186  	err = ht.host.lockStorageObligation(so)
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	err = ht.host.addStorageObligation(so)
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	err = ht.host.unlockStorageObligation(so)
   195  	if err != nil {
   196  		t.Fatal(err)
   197  	}
   198  	// Storage obligation should not be marked as having the transaction
   199  	// confirmed on the blockchain.
   200  	if so.OriginConfirmed {
   201  		t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way")
   202  	}
   203  
   204  	// Add a file contract revision, moving over a small amount of money to pay
   205  	// for the file contract.
   206  	sectorRoot, sectorData, err := randSector()
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	so.SectorRoots = []crypto.Hash{sectorRoot}
   211  	sectorCost := types.SiacoinPrecision.Mul64(550)
   212  	so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost)
   213  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost)
   214  	validPayouts, missedPayouts := so.payouts()
   215  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost)
   216  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost)
   217  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost)
   218  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost)
   219  	revisionSet := []types.Transaction{{
   220  		FileContractRevisions: []types.FileContractRevision{{
   221  			ParentID:          so.id(),
   222  			UnlockConditions:  types.UnlockConditions{},
   223  			NewRevisionNumber: 1,
   224  
   225  			NewFileSize:           uint64(len(sectorData)),
   226  			NewFileMerkleRoot:     sectorRoot,
   227  			NewWindowStart:        so.expiration(),
   228  			NewWindowEnd:          so.proofDeadline(),
   229  			NewValidProofOutputs:  validPayouts,
   230  			NewMissedProofOutputs: missedPayouts,
   231  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   232  		}},
   233  	}}
   234  	err = ht.host.lockStorageObligation(so)
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData})
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  	err = ht.host.unlockStorageObligation(so)
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  	// Submit the revision set to the transaction pool.
   247  	err = ht.tpool.AcceptTransactionSet(revisionSet)
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  
   252  	// Mine a block to confirm the transactions containing the file contract
   253  	// and the file contract revision.
   254  	_, err = ht.miner.AddBlock()
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  	// Load the storage obligation from the database, see if it updated
   259  	// correctly.
   260  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   261  		*so, err = getStorageObligation(tx, so.id())
   262  		if err != nil {
   263  			return err
   264  		}
   265  		return nil
   266  	})
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	if !so.OriginConfirmed {
   271  		t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined")
   272  	}
   273  	if !so.RevisionConfirmed {
   274  		t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined")
   275  	}
   276  
   277  	// Mine until the host submits a storage proof.
   278  	for i := ht.host.blockHeight; i <= so.expiration()+resubmissionTimeout; i++ {
   279  		_, err := ht.miner.AddBlock()
   280  		if err != nil {
   281  			t.Fatal(err)
   282  		}
   283  	}
   284  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   285  		*so, err = getStorageObligation(tx, so.id())
   286  		if err != nil {
   287  			return err
   288  		}
   289  		return nil
   290  	})
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	if !so.OriginConfirmed {
   295  		t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined")
   296  	}
   297  	if !so.RevisionConfirmed {
   298  		t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined")
   299  	}
   300  	if !so.ProofConfirmed {
   301  		t.Fatal("storage obligation is not saying that the storage proof was confirmed on the blockchain")
   302  	}
   303  
   304  	// Mine blocks until the storage proof has enough confirmations that the
   305  	// host will delete the file entirely.
   306  	for i := 0; i <= int(defaultWindowSize); i++ {
   307  		_, err := ht.miner.AddBlock()
   308  		if err != nil {
   309  			t.Fatal(err)
   310  		}
   311  	}
   312  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   313  		*so, err = getStorageObligation(tx, so.id())
   314  		if err != nil {
   315  			return err
   316  		}
   317  		return nil
   318  	})
   319  	if err != errNoStorageObligation {
   320  		t.Fatal(err)
   321  	}
   322  	if ht.host.financialMetrics.StorageRevenue.Cmp(sectorCost) != 0 {
   323  		t.Fatal("the host should be reporting revenue after a successful storage proof")
   324  	}
   325  }
   326  
   327  // TestMultiSectorObligationStack checks that the host correctly manages a
   328  // storage obligation with a single sector, the revision is created the same
   329  // block as the file contract.
   330  //
   331  // Unlike the SingleSector test, the multi sector test attempts to spread file
   332  // contract revisions over multiple blocks.
   333  func TestMultiSectorStorageObligationStack(t *testing.T) {
   334  	if testing.Short() {
   335  		t.SkipNow()
   336  	}
   337  	t.Parallel()
   338  	ht, err := newHostTester("TestMultiSectorStorageObligationStack")
   339  	if err != nil {
   340  		t.Fatal(err)
   341  	}
   342  
   343  	// Start by adding a storage obligation to the host. To emulate conditions
   344  	// of a renter creating the first contract, the storage obligation has no
   345  	// data, but does have money.
   346  	so, err := ht.newTesterStorageObligation()
   347  	if err != nil {
   348  		t.Fatal(err)
   349  	}
   350  	err = ht.host.lockStorageObligation(so)
   351  	if err != nil {
   352  		t.Fatal(err)
   353  	}
   354  	err = ht.host.addStorageObligation(so)
   355  	if err != nil {
   356  		t.Fatal(err)
   357  	}
   358  	err = ht.host.unlockStorageObligation(so)
   359  	if err != nil {
   360  		t.Fatal(err)
   361  	}
   362  	// Storage obligation should not be marked as having the transaction
   363  	// confirmed on the blockchain.
   364  	if so.OriginConfirmed {
   365  		t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way")
   366  	}
   367  	// Deviation from SingleSector test - mine a block here to confirm the
   368  	// storage obligation before a file contract revision is created.
   369  	_, err = ht.miner.AddBlock()
   370  	if err != nil {
   371  		t.Fatal(err)
   372  	}
   373  	// Load the storage obligation from the database, see if it updated
   374  	// correctly.
   375  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   376  		*so, err = getStorageObligation(tx, so.id())
   377  		if err != nil {
   378  			return err
   379  		}
   380  		return nil
   381  	})
   382  	if err != nil {
   383  		t.Fatal(err)
   384  	}
   385  	if !so.OriginConfirmed {
   386  		t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined")
   387  	}
   388  
   389  	// Add a file contract revision, moving over a small amount of money to pay
   390  	// for the file contract.
   391  	sectorRoot, sectorData, err := randSector()
   392  	if err != nil {
   393  		t.Fatal(err)
   394  	}
   395  	so.SectorRoots = []crypto.Hash{sectorRoot}
   396  	sectorCost := types.SiacoinPrecision.Mul64(550)
   397  	so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost)
   398  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost)
   399  	validPayouts, missedPayouts := so.payouts()
   400  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost)
   401  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost)
   402  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost)
   403  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost)
   404  	revisionSet := []types.Transaction{{
   405  		FileContractRevisions: []types.FileContractRevision{{
   406  			ParentID:          so.id(),
   407  			UnlockConditions:  types.UnlockConditions{},
   408  			NewRevisionNumber: 1,
   409  
   410  			NewFileSize:           uint64(len(sectorData)),
   411  			NewFileMerkleRoot:     sectorRoot,
   412  			NewWindowStart:        so.expiration(),
   413  			NewWindowEnd:          so.proofDeadline(),
   414  			NewValidProofOutputs:  validPayouts,
   415  			NewMissedProofOutputs: missedPayouts,
   416  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   417  		}},
   418  	}}
   419  	err = ht.host.lockStorageObligation(so)
   420  	if err != nil {
   421  		t.Fatal(err)
   422  	}
   423  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData})
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	err = ht.host.unlockStorageObligation(so)
   428  	if err != nil {
   429  		t.Fatal(err)
   430  	}
   431  	// Submit the revision set to the transaction pool.
   432  	err = ht.tpool.AcceptTransactionSet(revisionSet)
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  
   437  	// Create a second file contract revision, which is going to be submitted
   438  	// to the transaction pool after the first revision. Though, in practice
   439  	// this should never happen, we want to check that the transaction pool is
   440  	// correctly handling multiple file contract revisions being submitted in
   441  	// the same block cycle. This test will additionally tell us whether or not
   442  	// the host can correctly handle buildling storage proofs for files with
   443  	// multiple sectors.
   444  	sectorRoot2, sectorData2, err := randSector()
   445  	if err != nil {
   446  		t.Fatal(err)
   447  	}
   448  	so.SectorRoots = []crypto.Hash{sectorRoot, sectorRoot2}
   449  	sectorCost2 := types.SiacoinPrecision.Mul64(650)
   450  	so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost2)
   451  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost2)
   452  	validPayouts, missedPayouts = so.payouts()
   453  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost2)
   454  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost2)
   455  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost2)
   456  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost2)
   457  	combinedSectors := append(sectorData, sectorData2...)
   458  	combinedRoot := crypto.MerkleRoot(combinedSectors)
   459  	revisionSet2 := []types.Transaction{{
   460  		FileContractRevisions: []types.FileContractRevision{{
   461  			ParentID:          so.id(),
   462  			UnlockConditions:  types.UnlockConditions{},
   463  			NewRevisionNumber: 2,
   464  
   465  			NewFileSize:           uint64(len(sectorData) + len(sectorData2)),
   466  			NewFileMerkleRoot:     combinedRoot,
   467  			NewWindowStart:        so.expiration(),
   468  			NewWindowEnd:          so.proofDeadline(),
   469  			NewValidProofOutputs:  validPayouts,
   470  			NewMissedProofOutputs: missedPayouts,
   471  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   472  		}},
   473  	}}
   474  	err = ht.host.lockStorageObligation(so)
   475  	if err != nil {
   476  		t.Fatal(err)
   477  	}
   478  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot2}, [][]byte{sectorData2})
   479  	if err != nil {
   480  		t.Fatal(err)
   481  	}
   482  	err = ht.host.unlockStorageObligation(so)
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  	// Submit the revision set to the transaction pool.
   487  	err = ht.tpool.AcceptTransactionSet(revisionSet2)
   488  	if err != nil {
   489  		t.Fatal(err)
   490  	}
   491  
   492  	// Mine a block to confirm the transactions containing the file contract
   493  	// and the file contract revision.
   494  	_, err = ht.miner.AddBlock()
   495  	if err != nil {
   496  		t.Fatal(err)
   497  	}
   498  	// Load the storage obligation from the database, see if it updated
   499  	// correctly.
   500  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   501  		*so, err = getStorageObligation(tx, so.id())
   502  		if err != nil {
   503  			return err
   504  		}
   505  		return nil
   506  	})
   507  	if err != nil {
   508  		t.Fatal(err)
   509  	}
   510  	if !so.OriginConfirmed {
   511  		t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined")
   512  	}
   513  	if !so.RevisionConfirmed {
   514  		t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined")
   515  	}
   516  
   517  	// Mine until the host submits a storage proof.
   518  	for i := ht.host.blockHeight; i <= so.expiration()+resubmissionTimeout; i++ {
   519  		_, err := ht.miner.AddBlock()
   520  		if err != nil {
   521  			t.Fatal(err)
   522  		}
   523  	}
   524  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   525  		*so, err = getStorageObligation(tx, so.id())
   526  		if err != nil {
   527  			return err
   528  		}
   529  		return nil
   530  	})
   531  	if err != nil {
   532  		t.Fatal(err)
   533  	}
   534  	if !so.OriginConfirmed {
   535  		t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined")
   536  	}
   537  	if !so.RevisionConfirmed {
   538  		t.Fatal("revision transaction for storage obligation was not confirmed after a block was mined")
   539  	}
   540  	if !so.ProofConfirmed {
   541  		t.Fatal("storage obligation is not saying that the storage proof was confirmed on the blockchain")
   542  	}
   543  
   544  	// Mine blocks until the storage proof has enough confirmations that the
   545  	// host will delete the file entirely.
   546  	for i := 0; i <= int(defaultWindowSize); i++ {
   547  		_, err := ht.miner.AddBlock()
   548  		if err != nil {
   549  			t.Fatal(err)
   550  		}
   551  	}
   552  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   553  		*so, err = getStorageObligation(tx, so.id())
   554  		if err != nil {
   555  			return err
   556  		}
   557  		return nil
   558  	})
   559  	if err != errNoStorageObligation {
   560  		t.Fatal(err)
   561  	}
   562  	if ht.host.financialMetrics.StorageRevenue.Cmp(sectorCost.Add(sectorCost2)) != 0 {
   563  		t.Fatal("the host should be reporting revenue after a successful storage proof")
   564  	}
   565  }
   566  
   567  // TestAutoRevisionSubmission checks that the host correctly submits a file
   568  // contract revision to the consensus set.
   569  func TestAutoRevisionSubmission(t *testing.T) {
   570  	if testing.Short() {
   571  		t.SkipNow()
   572  	}
   573  	t.Parallel()
   574  	ht, err := newHostTester("TestAutoRevisionSubmission")
   575  	if err != nil {
   576  		t.Fatal(err)
   577  	}
   578  
   579  	// Start by adding a storage obligation to the host. To emulate conditions
   580  	// of a renter creating the first contract, the storage obligation has no
   581  	// data, but does have money.
   582  	so, err := ht.newTesterStorageObligation()
   583  	if err != nil {
   584  		t.Fatal(err)
   585  	}
   586  	err = ht.host.lockStorageObligation(so)
   587  	if err != nil {
   588  		t.Fatal(err)
   589  	}
   590  	err = ht.host.addStorageObligation(so)
   591  	if err != nil {
   592  		t.Fatal(err)
   593  	}
   594  	err = ht.host.unlockStorageObligation(so)
   595  	if err != nil {
   596  		t.Fatal(err)
   597  	}
   598  	// Storage obligation should not be marked as having the transaction
   599  	// confirmed on the blockchain.
   600  	if so.OriginConfirmed {
   601  		t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way")
   602  	}
   603  
   604  	// Add a file contract revision, moving over a small amount of money to pay
   605  	// for the file contract.
   606  	sectorRoot, sectorData, err := randSector()
   607  	if err != nil {
   608  		t.Fatal(err)
   609  	}
   610  	so.SectorRoots = []crypto.Hash{sectorRoot}
   611  	sectorCost := types.SiacoinPrecision.Mul64(550)
   612  	so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost)
   613  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost)
   614  	validPayouts, missedPayouts := so.payouts()
   615  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost)
   616  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost)
   617  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost)
   618  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost)
   619  	revisionSet := []types.Transaction{{
   620  		FileContractRevisions: []types.FileContractRevision{{
   621  			ParentID:          so.id(),
   622  			UnlockConditions:  types.UnlockConditions{},
   623  			NewRevisionNumber: 1,
   624  
   625  			NewFileSize:           uint64(len(sectorData)),
   626  			NewFileMerkleRoot:     sectorRoot,
   627  			NewWindowStart:        so.expiration(),
   628  			NewWindowEnd:          so.proofDeadline(),
   629  			NewValidProofOutputs:  validPayouts,
   630  			NewMissedProofOutputs: missedPayouts,
   631  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   632  		}},
   633  	}}
   634  	so.RevisionTransactionSet = revisionSet
   635  	err = ht.host.lockStorageObligation(so)
   636  	if err != nil {
   637  		t.Fatal(err)
   638  	}
   639  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData})
   640  	if err != nil {
   641  		t.Fatal(err)
   642  	}
   643  	err = ht.host.unlockStorageObligation(so)
   644  	if err != nil {
   645  		t.Fatal(err)
   646  	}
   647  	// Unlike the other tests, this test does not submit the file contract
   648  	// revision to the transaction pool for the host, the host is expected to
   649  	// do it automatically.
   650  
   651  	// Mine until the host submits a storage proof.
   652  	for i := types.BlockHeight(0); i <= revisionSubmissionBuffer+2+resubmissionTimeout; i++ {
   653  		_, err := ht.miner.AddBlock()
   654  		if err != nil {
   655  			t.Fatal(err)
   656  		}
   657  	}
   658  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   659  		*so, err = getStorageObligation(tx, so.id())
   660  		if err != nil {
   661  			return err
   662  		}
   663  		return nil
   664  	})
   665  	if err != nil {
   666  		t.Fatal(err)
   667  	}
   668  	if !so.OriginConfirmed {
   669  		t.Fatal("origin transaction for storage obligation was not confirmed after blocks were mined")
   670  	}
   671  	if !so.RevisionConfirmed {
   672  		t.Fatal("revision transaction for storage obligation was not confirmed after blocks were mined")
   673  	}
   674  	if !so.ProofConfirmed {
   675  		t.Fatal("storage obligation is not saying that the storage proof was confirmed on the blockchain")
   676  	}
   677  
   678  	// Mine blocks until the storage proof has enough confirmations that the
   679  	// host will delete the file entirely.
   680  	for i := 0; i <= int(defaultWindowSize); i++ {
   681  		_, err := ht.miner.AddBlock()
   682  		if err != nil {
   683  			t.Fatal(err)
   684  		}
   685  	}
   686  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   687  		*so, err = getStorageObligation(tx, so.id())
   688  		if err != nil {
   689  			return err
   690  		}
   691  		return nil
   692  	})
   693  	if err != errNoStorageObligation {
   694  		t.Error(so.OriginConfirmed)
   695  		t.Error(so.RevisionConfirmed)
   696  		t.Error(so.ProofConfirmed)
   697  		t.Fatal(err)
   698  	}
   699  	if ht.host.financialMetrics.StorageRevenue.Cmp(sectorCost) != 0 {
   700  		t.Fatal("the host should be reporting revenue after a successful storage proof")
   701  	}
   702  }