github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/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  	"SiaPrime/build"
    14  	"SiaPrime/crypto"
    15  	"SiaPrime/modules"
    16  	"SiaPrime/types"
    17  	"gitlab.com/NebulousLabs/fastrand"
    18  
    19  	"gitlab.com/NebulousLabs/bolt"
    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.mu.Lock()
   227  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost)
   228  	ht.host.mu.Unlock()
   229  	validPayouts, missedPayouts := so.payouts()
   230  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost)
   231  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost)
   232  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost)
   233  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost)
   234  	revisionSet := []types.Transaction{{
   235  		FileContractRevisions: []types.FileContractRevision{{
   236  			ParentID:          so.id(),
   237  			UnlockConditions:  types.UnlockConditions{},
   238  			NewRevisionNumber: 1,
   239  
   240  			NewFileSize:           uint64(len(sectorData)),
   241  			NewFileMerkleRoot:     sectorRoot,
   242  			NewWindowStart:        so.expiration(),
   243  			NewWindowEnd:          so.proofDeadline(),
   244  			NewValidProofOutputs:  validPayouts,
   245  			NewMissedProofOutputs: missedPayouts,
   246  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   247  		}},
   248  	}}
   249  	ht.host.managedLockStorageObligation(so.id())
   250  	ht.host.mu.Lock()
   251  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData})
   252  	ht.host.mu.Unlock()
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  	ht.host.managedUnlockStorageObligation(so.id())
   257  	// Submit the revision set to the transaction pool.
   258  	err = ht.tpool.AcceptTransactionSet(revisionSet)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	// Mine a block to confirm the transactions containing the file contract
   264  	// and the file contract revision.
   265  	_, err = ht.miner.AddBlock()
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	// Load the storage obligation from the database, see if it updated
   270  	// correctly.
   271  	err = build.Retry(100, 100*time.Millisecond, func() error {
   272  		ht.host.mu.Lock()
   273  		err := ht.host.db.View(func(tx *bolt.Tx) error {
   274  			so, err = getStorageObligation(tx, so.id())
   275  			if err != nil {
   276  				return err
   277  			}
   278  			return nil
   279  		})
   280  		ht.host.mu.Unlock()
   281  		if err != nil {
   282  			return err
   283  		}
   284  		if !so.OriginConfirmed {
   285  			return errors.New("origin transaction for storage obligation was not confirmed after a block was mined")
   286  		}
   287  		if !so.RevisionConfirmed {
   288  			return errors.New("revision transaction for storage obligation was not confirmed after a block was mined")
   289  		}
   290  		return nil
   291  	})
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	// Mine until the host submits a storage proof.
   297  	ht.host.mu.Lock()
   298  	bh := ht.host.blockHeight
   299  	ht.host.mu.Unlock()
   300  	for i := bh; i <= so.expiration()+resubmissionTimeout; i++ {
   301  		_, err := ht.miner.AddBlock()
   302  		if err != nil {
   303  			t.Fatal(err)
   304  		}
   305  	}
   306  	// Flush the host - flush will block until the host has submitted the
   307  	// storage proof to the transaction pool.
   308  	err = ht.host.tg.Flush()
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  	// Mine another block, to get the storage proof from the transaction pool
   313  	// into the blockchain.
   314  	_, err = ht.miner.AddBlock()
   315  	if err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	// Grab the storage proof and inspect the contents.
   320  	err = build.Retry(100, 100*time.Millisecond, func() error {
   321  		ht.host.mu.Lock()
   322  		err = ht.host.db.View(func(tx *bolt.Tx) error {
   323  			so, err = getStorageObligation(tx, so.id())
   324  			if err != nil {
   325  				return err
   326  			}
   327  			return nil
   328  		})
   329  		ht.host.mu.Unlock()
   330  		if err != nil {
   331  			return err
   332  		}
   333  		if !so.OriginConfirmed {
   334  			return errors.New("origin transaction for storage obligation was not confirmed after a block was mined")
   335  		}
   336  		if !so.RevisionConfirmed {
   337  			return errors.New("revision transaction for storage obligation was not confirmed after a block was mined")
   338  		}
   339  		if !so.ProofConfirmed {
   340  			return errors.New("storage obligation is not saying that the storage proof was confirmed on the blockchain")
   341  		}
   342  		return nil
   343  	})
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  
   348  	// Mine blocks until the storage proof has enough confirmations that the
   349  	// host will finalize the obligation.
   350  	for i := 0; i <= int(defaultWindowSize); i++ {
   351  		_, err := ht.miner.AddBlock()
   352  		if err != nil {
   353  			t.Fatal(err)
   354  		}
   355  	}
   356  	ht.host.mu.Lock()
   357  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   358  		so, err = getStorageObligation(tx, so.id())
   359  		if err != nil {
   360  			return err
   361  		}
   362  		if so.SectorRoots != nil {
   363  			t.Error("sector roots were not cleared when the host finalized the obligation")
   364  		}
   365  		if so.ObligationStatus != obligationSucceeded {
   366  			t.Error("obligation is not being reported as successful:", so.ObligationStatus)
   367  		}
   368  		return nil
   369  	})
   370  	ht.host.mu.Unlock()
   371  	if err != nil {
   372  		t.Fatal(err)
   373  	}
   374  	ht.host.mu.Lock()
   375  	storageRevenue := ht.host.financialMetrics.StorageRevenue
   376  	ht.host.mu.Unlock()
   377  	if !storageRevenue.Equals(sectorCost) {
   378  		t.Fatal("the host should be reporting revenue after a successful storage proof")
   379  	}
   380  }
   381  
   382  // TestMultiSectorObligationStack checks that the host correctly manages a
   383  // storage obligation with a single sector, the revision is created the same
   384  // block as the file contract.
   385  //
   386  // Unlike the SingleSector test, the multi sector test attempts to spread file
   387  // contract revisions over multiple blocks.
   388  func TestMultiSectorStorageObligationStack(t *testing.T) {
   389  	if testing.Short() {
   390  		t.SkipNow()
   391  	}
   392  	t.Parallel()
   393  	ht, err := newHostTester("TestMultiSectorStorageObligationStack")
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  	defer ht.Close()
   398  
   399  	// Start by adding a storage obligation to the host. To emulate conditions
   400  	// of a renter creating the first contract, the storage obligation has no
   401  	// data, but does have money.
   402  	so, err := ht.newTesterStorageObligation()
   403  	if err != nil {
   404  		t.Fatal(err)
   405  	}
   406  	ht.host.managedLockStorageObligation(so.id())
   407  	err = ht.host.managedAddStorageObligation(so)
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  	ht.host.managedUnlockStorageObligation(so.id())
   412  	// Storage obligation should not be marked as having the transaction
   413  	// confirmed on the blockchain.
   414  	if so.OriginConfirmed {
   415  		t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way")
   416  	}
   417  	// Deviation from SingleSector test - mine a block here to confirm the
   418  	// storage obligation before a file contract revision is created.
   419  	_, err = ht.miner.AddBlock()
   420  	if err != nil {
   421  		t.Fatal(err)
   422  	}
   423  	// Load the storage obligation from the database, see if it updated
   424  	// correctly.
   425  	ht.host.mu.Lock()
   426  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   427  		so, err = getStorageObligation(tx, so.id())
   428  		if err != nil {
   429  			return err
   430  		}
   431  		return nil
   432  	})
   433  	ht.host.mu.Unlock()
   434  	if err != nil {
   435  		t.Fatal(err)
   436  	}
   437  	if !so.OriginConfirmed {
   438  		t.Fatal("origin transaction for storage obligation was not confirmed after a block was mined")
   439  	}
   440  
   441  	// Add a file contract revision, moving over a small amount of money to pay
   442  	// for the file contract.
   443  	sectorRoot, sectorData := randSector()
   444  	so.SectorRoots = []crypto.Hash{sectorRoot}
   445  	sectorCost := types.SiacoinPrecision.Mul64(550)
   446  	so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost)
   447  	ht.host.mu.Lock()
   448  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost)
   449  	ht.host.mu.Unlock()
   450  	validPayouts, missedPayouts := so.payouts()
   451  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost)
   452  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost)
   453  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost)
   454  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost)
   455  	revisionSet := []types.Transaction{{
   456  		FileContractRevisions: []types.FileContractRevision{{
   457  			ParentID:          so.id(),
   458  			UnlockConditions:  types.UnlockConditions{},
   459  			NewRevisionNumber: 1,
   460  
   461  			NewFileSize:           uint64(len(sectorData)),
   462  			NewFileMerkleRoot:     sectorRoot,
   463  			NewWindowStart:        so.expiration(),
   464  			NewWindowEnd:          so.proofDeadline(),
   465  			NewValidProofOutputs:  validPayouts,
   466  			NewMissedProofOutputs: missedPayouts,
   467  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   468  		}},
   469  	}}
   470  	ht.host.managedLockStorageObligation(so.id())
   471  	ht.host.mu.Lock()
   472  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData})
   473  	ht.host.mu.Unlock()
   474  	if err != nil {
   475  		t.Fatal(err)
   476  	}
   477  	ht.host.managedUnlockStorageObligation(so.id())
   478  	// Submit the revision set to the transaction pool.
   479  	err = ht.tpool.AcceptTransactionSet(revisionSet)
   480  	if err != nil {
   481  		t.Fatal(err)
   482  	}
   483  
   484  	// Create a second file contract revision, which is going to be submitted
   485  	// to the transaction pool after the first revision. Though, in practice
   486  	// this should never happen, we want to check that the transaction pool is
   487  	// correctly handling multiple file contract revisions being submitted in
   488  	// the same block cycle. This test will additionally tell us whether or not
   489  	// the host can correctly handle building storage proofs for files with
   490  	// multiple sectors.
   491  	sectorRoot2, sectorData2 := randSector()
   492  	so.SectorRoots = []crypto.Hash{sectorRoot, sectorRoot2}
   493  	sectorCost2 := types.SiacoinPrecision.Mul64(650)
   494  	so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost2)
   495  	ht.host.mu.Lock()
   496  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost2)
   497  	ht.host.mu.Unlock()
   498  	validPayouts, missedPayouts = so.payouts()
   499  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost2)
   500  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost2)
   501  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost2)
   502  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost2)
   503  	combinedSectors := append(sectorData, sectorData2...)
   504  	combinedRoot := crypto.MerkleRoot(combinedSectors)
   505  	revisionSet2 := []types.Transaction{{
   506  		FileContractRevisions: []types.FileContractRevision{{
   507  			ParentID:          so.id(),
   508  			UnlockConditions:  types.UnlockConditions{},
   509  			NewRevisionNumber: 2,
   510  
   511  			NewFileSize:           uint64(len(sectorData) + len(sectorData2)),
   512  			NewFileMerkleRoot:     combinedRoot,
   513  			NewWindowStart:        so.expiration(),
   514  			NewWindowEnd:          so.proofDeadline(),
   515  			NewValidProofOutputs:  validPayouts,
   516  			NewMissedProofOutputs: missedPayouts,
   517  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   518  		}},
   519  	}}
   520  	ht.host.managedLockStorageObligation(so.id())
   521  	ht.host.mu.Lock()
   522  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot2}, [][]byte{sectorData2})
   523  	ht.host.mu.Unlock()
   524  	if err != nil {
   525  		t.Fatal(err)
   526  	}
   527  	ht.host.managedUnlockStorageObligation(so.id())
   528  	// Submit the revision set to the transaction pool.
   529  	err = ht.tpool.AcceptTransactionSet(revisionSet2)
   530  	if err != nil {
   531  		t.Fatal(err)
   532  	}
   533  
   534  	// Mine a block to confirm the transactions containing the file contract
   535  	// and the file contract revision.
   536  	_, err = ht.miner.AddBlock()
   537  	if err != nil {
   538  		t.Fatal(err)
   539  	}
   540  	// Load the storage obligation from the database, see if it updated
   541  	// correctly.
   542  	err = build.Retry(100, 100*time.Millisecond, func() error {
   543  		ht.host.mu.Lock()
   544  		err := ht.host.db.View(func(tx *bolt.Tx) error {
   545  			so, err = getStorageObligation(tx, so.id())
   546  			if err != nil {
   547  				return err
   548  			}
   549  			return nil
   550  		})
   551  		ht.host.mu.Unlock()
   552  		if err != nil {
   553  			return err
   554  		}
   555  		if !so.OriginConfirmed {
   556  			return errors.New("origin transaction for storage obligation was not confirmed after a block was mined")
   557  		}
   558  		if !so.RevisionConfirmed {
   559  			return errors.New("revision transaction for storage obligation was not confirmed after a block was mined")
   560  		}
   561  		return nil
   562  	})
   563  	if err != nil {
   564  		t.Fatal(err)
   565  	}
   566  
   567  	// Mine until the host submits a storage proof.
   568  	ht.host.mu.Lock()
   569  	bh := ht.host.blockHeight
   570  	ht.host.mu.Unlock()
   571  	for i := bh; i <= so.expiration()+resubmissionTimeout; i++ {
   572  		_, err := ht.miner.AddBlock()
   573  		if err != nil {
   574  			t.Fatal(err)
   575  		}
   576  	}
   577  	// Flush the host - flush will block until the host has submitted the
   578  	// storage proof to the transaction pool.
   579  	err = ht.host.tg.Flush()
   580  	if err != nil {
   581  		t.Fatal(err)
   582  	}
   583  	// Mine another block, to get the storage proof from the transaction pool
   584  	// into the blockchain.
   585  	_, err = ht.miner.AddBlock()
   586  	if err != nil {
   587  		t.Fatal(err)
   588  	}
   589  
   590  	err = build.Retry(100, 100*time.Millisecond, func() error {
   591  		ht.host.mu.Lock()
   592  		err := ht.host.db.View(func(tx *bolt.Tx) error {
   593  			so, err = getStorageObligation(tx, so.id())
   594  			if err != nil {
   595  				return err
   596  			}
   597  			return nil
   598  		})
   599  		ht.host.mu.Unlock()
   600  		if err != nil {
   601  			return (err)
   602  		}
   603  		if !so.OriginConfirmed {
   604  			return errors.New("origin transaction for storage obligation was not confirmed after a block was mined")
   605  		}
   606  		if !so.RevisionConfirmed {
   607  			return errors.New("revision transaction for storage obligation was not confirmed after a block was mined")
   608  		}
   609  		if !so.ProofConfirmed {
   610  			return errors.New("storage obligation is not saying that the storage proof was confirmed on the blockchain")
   611  		}
   612  		return nil
   613  	})
   614  	if err != nil {
   615  		t.Fatal(err)
   616  	}
   617  
   618  	// Mine blocks until the storage proof has enough confirmations that the
   619  	// host will delete the file entirely.
   620  	for i := 0; i <= int(defaultWindowSize); i++ {
   621  		_, err := ht.miner.AddBlock()
   622  		if err != nil {
   623  			t.Fatal(err)
   624  		}
   625  	}
   626  	ht.host.mu.Lock()
   627  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   628  		so, err = getStorageObligation(tx, so.id())
   629  		if err != nil {
   630  			return err
   631  		}
   632  		if so.SectorRoots != nil {
   633  			t.Error("sector roots were not cleared out when the storage proof was finalized")
   634  		}
   635  		if so.ObligationStatus != obligationSucceeded {
   636  			t.Error("storage obligation was not reported as a success")
   637  		}
   638  		return nil
   639  	})
   640  	ht.host.mu.Unlock()
   641  	if err != nil {
   642  		t.Fatal(err)
   643  	}
   644  	if !ht.host.financialMetrics.StorageRevenue.Equals(sectorCost.Add(sectorCost2)) {
   645  		t.Fatal("the host should be reporting revenue after a successful storage proof")
   646  	}
   647  }
   648  
   649  // TestAutoRevisionSubmission checks that the host correctly submits a file
   650  // contract revision to the consensus set.
   651  func TestAutoRevisionSubmission(t *testing.T) {
   652  	if testing.Short() || !build.VLONG {
   653  		t.SkipNow()
   654  	}
   655  	t.Parallel()
   656  	ht, err := newHostTester("TestAutoRevisionSubmission")
   657  	if err != nil {
   658  		t.Fatal(err)
   659  	}
   660  	defer ht.Close()
   661  
   662  	// Start by adding a storage obligation to the host. To emulate conditions
   663  	// of a renter creating the first contract, the storage obligation has no
   664  	// data, but does have money.
   665  	so, err := ht.newTesterStorageObligation()
   666  	if err != nil {
   667  		t.Fatal(err)
   668  	}
   669  	ht.host.managedLockStorageObligation(so.id())
   670  	err = ht.host.managedAddStorageObligation(so)
   671  	if err != nil {
   672  		t.Fatal(err)
   673  	}
   674  	ht.host.managedUnlockStorageObligation(so.id())
   675  	// Storage obligation should not be marked as having the transaction
   676  	// confirmed on the blockchain.
   677  	if so.OriginConfirmed {
   678  		t.Fatal("storage obligation should not yet be marked as confirmed, confirmation is on the way")
   679  	}
   680  
   681  	// Add a file contract revision, moving over a small amount of money to pay
   682  	// for the file contract.
   683  	sectorRoot, sectorData := randSector()
   684  	so.SectorRoots = []crypto.Hash{sectorRoot}
   685  	sectorCost := types.SiacoinPrecision.Mul64(550)
   686  	so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(sectorCost)
   687  	ht.host.financialMetrics.PotentialStorageRevenue = ht.host.financialMetrics.PotentialStorageRevenue.Add(sectorCost)
   688  	validPayouts, missedPayouts := so.payouts()
   689  	validPayouts[0].Value = validPayouts[0].Value.Sub(sectorCost)
   690  	validPayouts[1].Value = validPayouts[1].Value.Add(sectorCost)
   691  	missedPayouts[0].Value = missedPayouts[0].Value.Sub(sectorCost)
   692  	missedPayouts[1].Value = missedPayouts[1].Value.Add(sectorCost)
   693  	revisionSet := []types.Transaction{{
   694  		FileContractRevisions: []types.FileContractRevision{{
   695  			ParentID:          so.id(),
   696  			UnlockConditions:  types.UnlockConditions{},
   697  			NewRevisionNumber: 1,
   698  
   699  			NewFileSize:           uint64(len(sectorData)),
   700  			NewFileMerkleRoot:     sectorRoot,
   701  			NewWindowStart:        so.expiration(),
   702  			NewWindowEnd:          so.proofDeadline(),
   703  			NewValidProofOutputs:  validPayouts,
   704  			NewMissedProofOutputs: missedPayouts,
   705  			NewUnlockHash:         types.UnlockConditions{}.UnlockHash(),
   706  		}},
   707  	}}
   708  	so.RevisionTransactionSet = revisionSet
   709  	ht.host.managedLockStorageObligation(so.id())
   710  	err = ht.host.modifyStorageObligation(so, nil, []crypto.Hash{sectorRoot}, [][]byte{sectorData})
   711  	if err != nil {
   712  		t.Fatal(err)
   713  	}
   714  	ht.host.managedUnlockStorageObligation(so.id())
   715  	err = ht.host.tg.Flush()
   716  	if err != nil {
   717  		t.Fatal(err)
   718  	}
   719  	// Unlike the other tests, this test does not submit the file contract
   720  	// revision to the transaction pool for the host, the host is expected to
   721  	// do it automatically.
   722  
   723  	// Mine until the host submits a storage proof.
   724  	for i := types.BlockHeight(0); i <= revisionSubmissionBuffer+2+resubmissionTimeout; i++ {
   725  		_, err := ht.miner.AddBlock()
   726  		if err != nil {
   727  			t.Fatal(err)
   728  		}
   729  		err = ht.host.tg.Flush()
   730  		if err != nil {
   731  			t.Fatal(err)
   732  		}
   733  	}
   734  	// Flush the host - flush will block until the host has submitted the
   735  	// storage proof to the transaction pool.
   736  	err = ht.host.tg.Flush()
   737  	if err != nil {
   738  		t.Fatal(err)
   739  	}
   740  	// Mine another block, to get the storage proof from the transaction pool
   741  	// into the blockchain.
   742  	_, err = ht.miner.AddBlock()
   743  	if err != nil {
   744  		t.Fatal(err)
   745  	}
   746  	err = ht.host.tg.Flush()
   747  	if err != nil {
   748  		t.Fatal(err)
   749  	}
   750  
   751  	err = build.Retry(50, 250*time.Millisecond, func() error {
   752  		err = ht.host.db.View(func(tx *bolt.Tx) error {
   753  			so, err = getStorageObligation(tx, so.id())
   754  			if err != nil {
   755  				return err
   756  			}
   757  			return nil
   758  		})
   759  		if err != nil {
   760  			return (err)
   761  		}
   762  		if !so.OriginConfirmed {
   763  			return errors.New("origin transaction for storage obligation was not confirmed after blocks were mined")
   764  		}
   765  		if !so.RevisionConfirmed {
   766  			return errors.New("revision transaction for storage obligation was not confirmed after blocks were mined")
   767  		}
   768  		if !so.ProofConfirmed {
   769  			return errors.New("storage obligation is not saying that the storage proof was confirmed on the blockchain")
   770  		}
   771  		return nil
   772  	})
   773  	if err != nil {
   774  		t.Fatal(err)
   775  	}
   776  
   777  	// Mine blocks until the storage proof has enough confirmations that the
   778  	// host will delete the file entirely.
   779  	for i := 0; i <= int(defaultWindowSize); i++ {
   780  		_, err := ht.miner.AddBlock()
   781  		if err != nil {
   782  			t.Fatal(err)
   783  		}
   784  		err = ht.host.tg.Flush()
   785  		if err != nil {
   786  			t.Fatal(err)
   787  		}
   788  	}
   789  	err = ht.host.db.View(func(tx *bolt.Tx) error {
   790  		so, err = getStorageObligation(tx, so.id())
   791  		if err != nil {
   792  			return err
   793  		}
   794  		if so.SectorRoots != nil {
   795  			t.Error("sector roots were not cleared out when the storage proof was finalized")
   796  		}
   797  		if so.ObligationStatus != obligationSucceeded {
   798  			t.Error("storage obligation was not reported as a success")
   799  		}
   800  		return nil
   801  	})
   802  	if err != nil {
   803  		t.Fatal(err)
   804  	}
   805  	if !ht.host.financialMetrics.StorageRevenue.Equals(sectorCost) {
   806  		t.Fatal("the host should be reporting revenue after a successful storage proof")
   807  	}
   808  }