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