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