gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/storageobligations.go (about)

     1  package host
     2  
     3  // storageobligations.go is responsible for managing the storage obligations
     4  // within the host - making sure that any file contracts, transaction
     5  // dependencies, file contract revisions, and storage proofs are making it into
     6  // the blockchain in a reasonable time.
     7  //
     8  // NOTE: Currently, the code partially supports changing the storage proof
     9  // window in file contract revisions, however the action item code will not
    10  // handle it correctly. Until the action item code is improved (to also handle
    11  // byzantine situations where the renter submits prior revisions), the host
    12  // should not support changing the storage proof window, especially to further
    13  // in the future.
    14  
    15  // TODO: Need to queue the action item for checking on the submission status of
    16  // the file contract revision. Also need to make sure that multiple actions are
    17  // being taken if needed.
    18  
    19  // TODO: Make sure that the origin tranasction set is not submitted to the
    20  // transaction pool before addSO is called - if it is, there will be a
    21  // duplicate transaction error, and then the storage obligation will return an
    22  // error, which is bad. Well, or perhas we just need to have better logic
    23  // handling.
    24  
    25  // TODO: Need to make sure that 'revision confirmed' is actually looking only
    26  // at the most recent revision (I think it is...)
    27  
    28  // TODO: Make sure that not too many action items are being created.
    29  
    30  // TODO: The ProofConstructed field of storageObligation
    31  // is not set or used.
    32  
    33  import (
    34  	"encoding/binary"
    35  	"encoding/json"
    36  	"errors"
    37  	"strconv"
    38  
    39  	bolt "github.com/coreos/bbolt"
    40  	"gitlab.com/SiaPrime/SiaPrime/build"
    41  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    42  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    43  	"gitlab.com/SiaPrime/SiaPrime/modules"
    44  	"gitlab.com/SiaPrime/SiaPrime/modules/wallet"
    45  	"gitlab.com/SiaPrime/SiaPrime/types"
    46  )
    47  
    48  const (
    49  	obligationUnresolved storageObligationStatus = iota // Indicatees that an unitialized value was used.
    50  	obligationRejected                                  // Indicates that the obligation never got started, no revenue gained or lost.
    51  	obligationSucceeded                                 // Indicates that the obligation was completed, revenues were gained.
    52  	obligationFailed                                    // Indicates that the obligation failed, revenues and collateral were lost.
    53  )
    54  
    55  var (
    56  	// errDuplicateStorageObligation is returned when the storage obligation
    57  	// database already has a storage obligation with the provided file
    58  	// contract. This error should only happen in the event of a developer
    59  	// mistake.
    60  	errDuplicateStorageObligation = errors.New("storage obligation has a file contract which conflicts with an existing storage obligation")
    61  
    62  	// errInsaneFileContractOutputCounts is returned when a file contract has
    63  	// the wrong number of outputs for either the valid or missed payouts.
    64  	errInsaneFileContractOutputCounts = errors.New("file contract has incorrect number of outputs for the valid or missed payouts")
    65  
    66  	// errInsaneFileContractRevisionOutputCounts is returned when a file
    67  	// contract has the wrong number of outputs for either the valid or missed
    68  	// payouts.
    69  	errInsaneFileContractRevisionOutputCounts = errors.New("file contract revision has incorrect number of outputs for the valid or missed payouts")
    70  
    71  	// errInsaneOriginSetFileContract is returned is the final transaction of
    72  	// the origin transaction set of a storage obligation does not have a file
    73  	// contract in the final transaction - there should be a file contract
    74  	// associated with every storage obligation.
    75  	errInsaneOriginSetFileContract = errors.New("origin transaction set of storage obligation should have one file contract in the final transaction")
    76  
    77  	// errInsaneOriginSetSize is returned if the origin transaction set of a
    78  	// storage obligation is empty - there should be a file contract associated
    79  	// with every storage obligation.
    80  	errInsaneOriginSetSize = errors.New("origin transaction set of storage obligation is size zero")
    81  
    82  	// errInsaneRevisionSetRevisionCount is returned if the final transaction
    83  	// in the revision transaction set of a storage obligation has more or less
    84  	// than one file contract revision.
    85  	errInsaneRevisionSetRevisionCount = errors.New("revision transaction set of storage obligation should have one file contract revision in the final transaction")
    86  
    87  	// errInsaneStorageObligationRevision is returned if there is an attempted
    88  	// storage obligation revision which does not have sensical inputs.
    89  	errInsaneStorageObligationRevision = errors.New("revision to storage obligation does not make sense")
    90  
    91  	// errInsaneStorageObligationRevisionData is returned if there is an
    92  	// attempted storage obligation revision which does not have sensical
    93  	// inputs.
    94  	errInsaneStorageObligationRevisionData = errors.New("revision to storage obligation has insane data")
    95  
    96  	// errNoBuffer is returned if there is an attempted storage obligation that
    97  	// needs to have the storage proof submitted in less than
    98  	// revisionSubmissionBuffer blocks.
    99  	errNoBuffer = errors.New("file contract rejected because storage proof window is too close")
   100  
   101  	// errNoStorageObligation is returned if the requested storage obligation
   102  	// is not found in the database.
   103  	errNoStorageObligation = errors.New("storage obligation not found in database")
   104  
   105  	// errObligationUnlocked is returned when a storage obligation is being
   106  	// removed from lock, but is already unlocked.
   107  	errObligationUnlocked = errors.New("storage obligation is unlocked, and should not be getting unlocked")
   108  )
   109  
   110  type storageObligationStatus uint64
   111  
   112  // storageObligation contains all of the metadata related to a file contract
   113  // and the storage contained by the file contract.
   114  type storageObligation struct {
   115  	// Storage obligations are broken up into ordered atomic sectors that are
   116  	// exactly 4MiB each. By saving the roots of each sector, storage proofs
   117  	// and modifications to the data can be made inexpensively by making use of
   118  	// the merkletree.CachedTree. Sectors can be appended, modified, or deleted
   119  	// and the host can recompute the Merkle root of the whole file without
   120  	// much computational or I/O expense.
   121  	SectorRoots []crypto.Hash
   122  
   123  	// Variables about the file contract that enforces the storage obligation.
   124  	// The origin an revision transaction are stored as a set, where the set
   125  	// contains potentially unconfirmed transactions.
   126  	ContractCost             types.Currency
   127  	LockedCollateral         types.Currency
   128  	PotentialDownloadRevenue types.Currency
   129  	PotentialStorageRevenue  types.Currency
   130  	PotentialUploadRevenue   types.Currency
   131  	RiskedCollateral         types.Currency
   132  	TransactionFeesAdded     types.Currency
   133  
   134  	// The negotiation height specifies the block height at which the file
   135  	// contract was negotiated. If the origin transaction set is not accepted
   136  	// onto the blockchain quickly enough, the contract is pruned from the
   137  	// host. The origin and revision transaction set contain the contracts +
   138  	// revisions as well as all parent transactions. The parents are necessary
   139  	// because after a restart the transaction pool may be emptied out.
   140  	NegotiationHeight      types.BlockHeight
   141  	OriginTransactionSet   []types.Transaction
   142  	RevisionTransactionSet []types.Transaction
   143  
   144  	// Variables indicating whether the critical transactions in a storage
   145  	// obligation have been confirmed on the blockchain.
   146  	ObligationStatus    storageObligationStatus
   147  	OriginConfirmed     bool
   148  	ProofConfirmed      bool
   149  	ProofConstructed    bool
   150  	RevisionConfirmed   bool
   151  	RevisionConstructed bool
   152  }
   153  
   154  func (i storageObligationStatus) String() string {
   155  	if i == 0 {
   156  		return "obligationUnresolved"
   157  	}
   158  	if i == 1 {
   159  		return "obligationRejected"
   160  	}
   161  	if i == 2 {
   162  		return "obligationSucceeded"
   163  	}
   164  	if i == 3 {
   165  		return "obligationFailed"
   166  	}
   167  	return "storageObligationStatus(" + strconv.FormatInt(int64(i), 10) + ")"
   168  }
   169  
   170  // getStorageObligation fetches a storage obligation from the database tx.
   171  func getStorageObligation(tx *bolt.Tx, soid types.FileContractID) (so storageObligation, err error) {
   172  	soBytes := tx.Bucket(bucketStorageObligations).Get(soid[:])
   173  	if soBytes == nil {
   174  		return storageObligation{}, errNoStorageObligation
   175  	}
   176  	err = json.Unmarshal(soBytes, &so)
   177  	if err != nil {
   178  		return storageObligation{}, err
   179  	}
   180  	return so, nil
   181  }
   182  
   183  // putStorageObligation places a storage obligation into the database,
   184  // overwriting the existing storage obligation if there is one.
   185  func putStorageObligation(tx *bolt.Tx, so storageObligation) error {
   186  	soBytes, err := json.Marshal(so)
   187  	if err != nil {
   188  		return err
   189  	}
   190  	soid := so.id()
   191  	return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes)
   192  }
   193  
   194  // expiration returns the height at which the storage obligation expires.
   195  func (so storageObligation) expiration() types.BlockHeight {
   196  	if len(so.RevisionTransactionSet) > 0 {
   197  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewWindowStart
   198  	}
   199  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].WindowStart
   200  }
   201  
   202  // fileSize returns the size of the data protected by the obligation.
   203  func (so storageObligation) fileSize() uint64 {
   204  	if len(so.RevisionTransactionSet) > 0 {
   205  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewFileSize
   206  	}
   207  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].FileSize
   208  }
   209  
   210  // id returns the id of the storage obligation, which is defined by the file
   211  // contract id of the file contract that governs the storage contract.
   212  func (so storageObligation) id() types.FileContractID {
   213  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContractID(0)
   214  }
   215  
   216  // isSane checks that required assumptions about the storage obligation are
   217  // correct.
   218  func (so storageObligation) isSane() error {
   219  	// There should be an origin transaction set.
   220  	if len(so.OriginTransactionSet) == 0 {
   221  		build.Critical("origin transaction set is empty")
   222  		return errInsaneOriginSetSize
   223  	}
   224  
   225  	// The final transaction of the origin transaction set should have one file
   226  	// contract.
   227  	final := len(so.OriginTransactionSet) - 1
   228  	fcCount := len(so.OriginTransactionSet[final].FileContracts)
   229  	if fcCount != 1 {
   230  		build.Critical("wrong number of file contracts associated with storage obligation:", fcCount)
   231  		return errInsaneOriginSetFileContract
   232  	}
   233  
   234  	// The file contract in the final transaction of the origin transaction set
   235  	// should have two valid proof outputs and two missed proof outputs.
   236  	lenVPOs := len(so.OriginTransactionSet[final].FileContracts[0].ValidProofOutputs)
   237  	lenMPOs := len(so.OriginTransactionSet[final].FileContracts[0].MissedProofOutputs)
   238  	if lenVPOs != 2 || lenMPOs != 2 {
   239  		build.Critical("file contract has wrong number of VPOs and MPOs, expecting 2 each:", lenVPOs, lenMPOs)
   240  		return errInsaneFileContractOutputCounts
   241  	}
   242  
   243  	// If there is a revision transaction set, there should be one file
   244  	// contract revision in the final transaction.
   245  	if len(so.RevisionTransactionSet) > 0 {
   246  		final = len(so.OriginTransactionSet) - 1
   247  		fcrCount := len(so.OriginTransactionSet[final].FileContractRevisions)
   248  		if fcrCount != 1 {
   249  			build.Critical("wrong number of file contract revisions in final transaction of revision transaction set:", fcrCount)
   250  			return errInsaneRevisionSetRevisionCount
   251  		}
   252  
   253  		// The file contract revision in the final transaction of the revision
   254  		// transaction set should have two valid proof outputs and two missed
   255  		// proof outputs.
   256  		lenVPOs = len(so.RevisionTransactionSet[final].FileContractRevisions[0].NewValidProofOutputs)
   257  		lenMPOs = len(so.RevisionTransactionSet[final].FileContractRevisions[0].NewMissedProofOutputs)
   258  		if lenVPOs != 2 || lenMPOs != 2 {
   259  			build.Critical("file contract has wrong number of VPOs and MPOs, expecting 2 each:", lenVPOs, lenMPOs)
   260  			return errInsaneFileContractRevisionOutputCounts
   261  		}
   262  	}
   263  	return nil
   264  }
   265  
   266  // merkleRoot returns the file merkle root of a storage obligation.
   267  func (so storageObligation) merkleRoot() crypto.Hash {
   268  	if len(so.RevisionTransactionSet) > 0 {
   269  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewFileMerkleRoot
   270  	}
   271  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].FileMerkleRoot
   272  }
   273  
   274  // payouts returns the set of valid payouts and missed payouts that represent
   275  // the latest revision for the storage obligation.
   276  func (so storageObligation) payouts() (valid []types.SiacoinOutput, missed []types.SiacoinOutput) {
   277  	valid = make([]types.SiacoinOutput, 2)
   278  	missed = make([]types.SiacoinOutput, 2)
   279  	if len(so.RevisionTransactionSet) > 0 {
   280  		copy(valid, so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewValidProofOutputs)
   281  		copy(missed, so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewMissedProofOutputs)
   282  		return
   283  	}
   284  	copy(valid, so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].ValidProofOutputs)
   285  	copy(missed, so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].MissedProofOutputs)
   286  	return
   287  }
   288  
   289  // proofDeadline returns the height by which the storage proof must be
   290  // submitted.
   291  func (so storageObligation) proofDeadline() types.BlockHeight {
   292  	if len(so.RevisionTransactionSet) > 0 {
   293  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewWindowEnd
   294  	}
   295  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].WindowEnd
   296  }
   297  
   298  // transactionID returns the ID of the transaction containing the file
   299  // contract.
   300  func (so storageObligation) transactionID() types.TransactionID {
   301  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].ID()
   302  }
   303  
   304  // value returns the value of fulfilling the storage obligation to the host.
   305  func (so storageObligation) value() types.Currency {
   306  	return so.ContractCost.Add(so.PotentialDownloadRevenue).Add(so.PotentialStorageRevenue).Add(so.PotentialUploadRevenue).Add(so.RiskedCollateral)
   307  }
   308  
   309  // deleteStorageObligations deletes obligations from the database.
   310  // It is assumed the deleted obligations don't belong in the database in the first place,
   311  // so no financial metrics are updated.
   312  func (h *Host) deleteStorageObligations(soids []types.FileContractID) error {
   313  	h.mu.RLock()
   314  	defer h.mu.RUnlock()
   315  	err := h.db.Update(func(tx *bolt.Tx) error {
   316  		// Delete obligations.
   317  		b := tx.Bucket(bucketStorageObligations)
   318  		for _, soid := range soids {
   319  			err := b.Delete([]byte(soid[:]))
   320  			if err != nil {
   321  				return build.ExtendErr("unable to delete transaction id:", err)
   322  			}
   323  		}
   324  		return nil
   325  	})
   326  	if err != nil {
   327  		h.log.Println(build.ExtendErr("database failed to delete storage obligations:", err))
   328  		return err
   329  	}
   330  	return nil
   331  }
   332  
   333  // queueActionItem adds an action item to the host at the input height so that
   334  // the host knows to perform maintenance on the associated storage obligation
   335  // when that height is reached.
   336  func (h *Host) queueActionItem(height types.BlockHeight, id types.FileContractID) error {
   337  	// Sanity check - action item should be at a higher height than the current
   338  	// block height.
   339  	if height <= h.blockHeight {
   340  		h.log.Println("action item queued improperly")
   341  	}
   342  	return h.db.Update(func(tx *bolt.Tx) error {
   343  		// Translate the height into a byte slice.
   344  		heightBytes := make([]byte, 8)
   345  		binary.BigEndian.PutUint64(heightBytes, uint64(height))
   346  
   347  		// Get the list of action items already at this height and extend it.
   348  		bai := tx.Bucket(bucketActionItems)
   349  		existingItems := bai.Get(heightBytes)
   350  		var extendedItems = make([]byte, len(existingItems), len(existingItems)+len(id[:]))
   351  		copy(extendedItems, existingItems)
   352  		extendedItems = append(extendedItems, id[:]...)
   353  		return bai.Put(heightBytes, extendedItems)
   354  	})
   355  }
   356  
   357  // managedAddStorageObligation adds a storage obligation to the host. Because
   358  // this operation can return errors, the transactions should not be submitted to
   359  // the blockchain until after this function has indicated success. All of the
   360  // sectors that are present in the storage obligation should already be on disk,
   361  // which means that addStorageObligation should be exclusively called when
   362  // creating a new, empty file contract or when renewing an existing file
   363  // contract.
   364  func (h *Host) managedAddStorageObligation(so storageObligation) error {
   365  	soid := so.id()
   366  
   367  	err := func() error {
   368  		h.mu.Lock()
   369  		defer h.mu.Unlock()
   370  
   371  		// Sanity check - obligation should be under lock while being added.
   372  		_, exists := h.lockedStorageObligations[soid]
   373  		if !exists {
   374  			h.log.Critical("addStorageObligation called with an obligation that is not locked")
   375  		}
   376  		// Sanity check - There needs to be enough time left on the file contract
   377  		// for the host to safely submit the file contract revision.
   378  		if h.blockHeight+revisionSubmissionBuffer >= so.expiration() {
   379  			h.log.Critical("submission window was not verified before trying to submit a storage obligation")
   380  			return errNoBuffer
   381  		}
   382  		// Sanity check - the resubmission timeout needs to be smaller than storage
   383  		// proof window.
   384  		if so.expiration()+resubmissionTimeout >= so.proofDeadline() {
   385  			h.log.Critical("host is misconfigured - the storage proof window needs to be long enough to resubmit if needed")
   386  			return errors.New("fill me in")
   387  		}
   388  
   389  		// Add the storage obligation information to the database.
   390  		err := h.db.Update(func(tx *bolt.Tx) error {
   391  			// Sanity check - a storage obligation using the same file contract id
   392  			// should not already exist. This situation can happen if the
   393  			// transaction pool ejects a file contract and then a new one is
   394  			// created. Though the file contract will have the same terms, some
   395  			// other conditions might cause problems. The check for duplicate file
   396  			// contract ids should happen during the negotiation phase, and not
   397  			// during the 'addStorageObligation' phase.
   398  			bso := tx.Bucket(bucketStorageObligations)
   399  
   400  			// If the storage obligation already has sectors, it means that the
   401  			// file contract is being renewed, and that the sector should be
   402  			// re-added with a new expiration height. If there is an error at any
   403  			// point, all of the sectors should be removed.
   404  			renewal := len(so.SectorRoots) > 0
   405  			if renewal {
   406  				err := h.AddSectorBatch(so.SectorRoots)
   407  				if err != nil {
   408  					return err
   409  				}
   410  			}
   411  
   412  			// Add the storage obligation to the database.
   413  			soBytes, err := json.Marshal(so)
   414  			if err == nil {
   415  				err = bso.Put(soid[:], soBytes)
   416  			}
   417  			if renewal && err != nil {
   418  				_ = h.RemoveSectorBatch(so.SectorRoots)
   419  			}
   420  			return err
   421  		})
   422  		return err
   423  	}()
   424  	if err != nil {
   425  		return err
   426  	}
   427  
   428  	// Check that the transaction is fully valid and submit it to the
   429  	// transaction pool.
   430  	err = h.tpool.AcceptTransactionSet(so.OriginTransactionSet)
   431  	if err != nil {
   432  		h.log.Println("Failed to add storage obligation, transaction set was not accepted:", err)
   433  		return err
   434  	}
   435  
   436  	// Queue the action items.
   437  	h.mu.Lock()
   438  	defer h.mu.Unlock()
   439  
   440  	// Update the host financial metrics with regards to this storage
   441  	// obligation.
   442  	h.financialMetrics.ContractCount++
   443  	h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Add(so.ContractCost)
   444  	h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Add(so.LockedCollateral)
   445  	h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Add(so.PotentialStorageRevenue)
   446  	h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue)
   447  	h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Add(so.PotentialUploadRevenue)
   448  	h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Add(so.RiskedCollateral)
   449  	h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Add(so.TransactionFeesAdded)
   450  
   451  	// The file contract was already submitted to the blockchain, need to check
   452  	// after the resubmission timeout that it was submitted successfully.
   453  	err1 := h.queueActionItem(h.blockHeight+resubmissionTimeout, soid)
   454  	err2 := h.queueActionItem(h.blockHeight+resubmissionTimeout*2, soid) // Paranoia
   455  	// Queue an action item to submit the file contract revision - if there is
   456  	// never a file contract revision, the handling of this action item will be
   457  	// a no-op.
   458  	err3 := h.queueActionItem(so.expiration()-revisionSubmissionBuffer, soid)
   459  	err4 := h.queueActionItem(so.expiration()-revisionSubmissionBuffer+resubmissionTimeout, soid) // Paranoia
   460  	// The storage proof should be submitted
   461  	err5 := h.queueActionItem(so.expiration()+resubmissionTimeout, soid)
   462  	err6 := h.queueActionItem(so.expiration()+resubmissionTimeout*2, soid) // Paranoia
   463  	err = composeErrors(err1, err2, err3, err4, err5, err6)
   464  	if err != nil {
   465  		h.log.Println("Error with transaction set, redacting obligation, id", so.id())
   466  		return composeErrors(err, h.removeStorageObligation(so, obligationRejected))
   467  	}
   468  	return nil
   469  }
   470  
   471  // modifyStorageObligation will take an updated storage obligation along with a
   472  // list of sector changes and update the database to account for all of it. The
   473  // sector modifications are only used to update the sector database, they will
   474  // not be used to modify the storage obligation (most importantly, this means
   475  // that sectorRoots needs to be updated by the calling function). Virtual
   476  // sectors will be removed the number of times that they are listed, to remove
   477  // multiple instances of the same virtual sector, the virtural sector will need
   478  // to appear in 'sectorsRemoved' multiple times. Same with 'sectorsGained'.
   479  func (h *Host) modifyStorageObligation(so storageObligation, sectorsRemoved []crypto.Hash, sectorsGained []crypto.Hash, gainedSectorData [][]byte) error {
   480  	// Sanity check - obligation should be under lock while being modified.
   481  	soid := so.id()
   482  	_, exists := h.lockedStorageObligations[soid]
   483  	if !exists {
   484  		h.log.Critical("modifyStorageObligation called with an obligation that is not locked")
   485  	}
   486  	// Sanity check - there needs to be enough time to submit the file contract
   487  	// revision to the blockchain.
   488  	if so.expiration()-revisionSubmissionBuffer <= h.blockHeight {
   489  		return errNoBuffer
   490  	}
   491  	// Sanity check - sectorsGained and gainedSectorData need to have the same length.
   492  	if len(sectorsGained) != len(gainedSectorData) {
   493  		h.log.Critical("modifying a revision with garbage sector data", len(sectorsGained), len(gainedSectorData))
   494  		return errInsaneStorageObligationRevision
   495  	}
   496  	// Sanity check - all of the sector data should be modules.SectorSize
   497  	for _, data := range gainedSectorData {
   498  		if uint64(len(data)) != modules.SectorSize {
   499  			h.log.Critical("modifying a revision with garbase sector sizes", len(data))
   500  			return errInsaneStorageObligationRevision
   501  		}
   502  	}
   503  
   504  	// Note, for safe error handling, the operation order should be: add
   505  	// sectors, update database, remove sectors. If the adding or update fails,
   506  	// the added sectors should be removed and the storage obligation shoud be
   507  	// considered invalid. If the removing fails, this is okay, it's ignored
   508  	// and left to consistency checks and user actions to fix (will reduce host
   509  	// capacity, but will not inhibit the host's ability to submit storage
   510  	// proofs)
   511  	var i int
   512  	var err error
   513  	for i = range sectorsGained {
   514  		err = h.AddSector(sectorsGained[i], gainedSectorData[i])
   515  		if err != nil {
   516  			break
   517  		}
   518  	}
   519  	if err != nil {
   520  		// Because there was an error, all of the sectors that got added need
   521  		// to be reverted.
   522  		for j := 0; j < i; j++ {
   523  			// Error is not checked because there's nothing useful that can be
   524  			// done about an error.
   525  			_ = h.RemoveSector(sectorsGained[j])
   526  		}
   527  		return err
   528  	}
   529  	// Update the database to contain the new storage obligation.
   530  	var oldSO storageObligation
   531  	err = h.db.Update(func(tx *bolt.Tx) error {
   532  		// Get the old storage obligation as a reference to know how to upate
   533  		// the host financial stats.
   534  		oldSO, err = getStorageObligation(tx, soid)
   535  		if err != nil {
   536  			return err
   537  		}
   538  
   539  		// Store the new storage obligation to replace the old one.
   540  		return putStorageObligation(tx, so)
   541  	})
   542  	if err != nil {
   543  		// Because there was an error, all of the sectors that got added need
   544  		// to be reverted.
   545  		for i := range sectorsGained {
   546  			// Error is not checked because there's nothing useful that can be
   547  			// done about an error.
   548  			_ = h.RemoveSector(sectorsGained[i])
   549  		}
   550  		return err
   551  	}
   552  	// Call removeSector for all of the sectors that have been removed.
   553  	for k := range sectorsRemoved {
   554  		// Error is not checkeed because there's nothing useful that can be
   555  		// done about an error. Failing to remove a sector is not a terrible
   556  		// place to be, especially if the host can run consistency checks.
   557  		_ = h.RemoveSector(sectorsRemoved[k])
   558  	}
   559  
   560  	// Update the financial information for the storage obligation - apply the
   561  	// new values.
   562  	h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Add(so.ContractCost)
   563  	h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Add(so.LockedCollateral)
   564  	h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Add(so.PotentialStorageRevenue)
   565  	h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue)
   566  	h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Add(so.PotentialUploadRevenue)
   567  	h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Add(so.RiskedCollateral)
   568  	h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Add(so.TransactionFeesAdded)
   569  
   570  	// Update the financial information for the storage obligation - remove the
   571  	// old values.
   572  	h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(oldSO.ContractCost)
   573  	h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(oldSO.LockedCollateral)
   574  	h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(oldSO.PotentialStorageRevenue)
   575  	h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(oldSO.PotentialDownloadRevenue)
   576  	h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(oldSO.PotentialUploadRevenue)
   577  	h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(oldSO.RiskedCollateral)
   578  	h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Sub(oldSO.TransactionFeesAdded)
   579  	return nil
   580  }
   581  
   582  // PruneStaleStorageObligations will delete storage obligations from the host
   583  // that, for whatever reason, did not make it on the block chain.
   584  // As these stale storage obligations have an impact on the host financial metrics,
   585  // this method updates the host financial metrics to show the correct values.
   586  func (h *Host) PruneStaleStorageObligations() error {
   587  	// Filter the stale obligations from the set of all obligations.
   588  	sos := h.StorageObligations()
   589  	var stale []types.FileContractID
   590  	for _, so := range sos {
   591  		conf, err := h.tpool.TransactionConfirmed(so.TransactionID)
   592  		if err != nil {
   593  			return build.ExtendErr("unable to get transaction ID:", err)
   594  		}
   595  		// An obligation is considered stale if it has not been confirmed
   596  		// within RespendTimeout blocks after negotiation.
   597  		if (h.blockHeight > so.NegotiationHeight+wallet.RespendTimeout) && !conf {
   598  			stale = append(stale, so.ObligationId)
   599  		}
   600  	}
   601  	// Delete stale obligations from the database.
   602  	err := h.deleteStorageObligations(stale)
   603  	if err != nil {
   604  		return build.ExtendErr("unable to delete stale storage ids:", err)
   605  	}
   606  	// Update the financial metrics of the host.
   607  	err = h.resetFinancialMetrics()
   608  	if err != nil {
   609  		h.log.Println(build.ExtendErr("unable to reset host financial metrics:", err))
   610  		return err
   611  	}
   612  	return nil
   613  }
   614  
   615  // removeStorageObligation will remove a storage obligation from the host,
   616  // either due to failure or success.
   617  func (h *Host) removeStorageObligation(so storageObligation, sos storageObligationStatus) error {
   618  	// Error is not checked, we want to call remove on every sector even if
   619  	// there are problems - disk health information will be updated.
   620  	_ = h.RemoveSectorBatch(so.SectorRoots)
   621  
   622  	// Update the host revenue metrics based on the status of the obligation.
   623  	if sos == obligationUnresolved {
   624  		h.log.Critical("storage obligation 'unresolved' during call to removeStorageObligation, id", so.id())
   625  	}
   626  	if sos == obligationRejected {
   627  		if h.financialMetrics.TransactionFeeExpenses.Cmp(so.TransactionFeesAdded) >= 0 {
   628  			h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Sub(so.TransactionFeesAdded)
   629  
   630  			// Remove the obligation statistics as potential risk and income.
   631  			h.log.Printf("Rejecting storage obligation expiring at block %v, current height is %v. Potential revenue is %v.\n", so.expiration(), h.blockHeight, h.financialMetrics.PotentialContractCompensation.Add(h.financialMetrics.PotentialStorageRevenue).Add(h.financialMetrics.PotentialDownloadBandwidthRevenue).Add(h.financialMetrics.PotentialUploadBandwidthRevenue))
   632  			h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost)
   633  			h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral)
   634  			h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue)
   635  			h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue)
   636  			h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue)
   637  			h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral)
   638  		}
   639  	}
   640  	if sos == obligationSucceeded {
   641  		// Empty obligations don't submit a storage proof. The revenue for an empty
   642  		// storage obligation should equal the contract cost of the obligation
   643  		revenue := so.ContractCost.Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue)
   644  		if len(so.SectorRoots) == 0 {
   645  			h.log.Printf("No need to submit a storage proof for empty contract. Revenue is %v.\n", revenue)
   646  		} else {
   647  			h.log.Printf("Successfully submitted a storage proof. Revenue is %v.\n", revenue)
   648  		}
   649  
   650  		// Remove the obligation statistics as potential risk and income.
   651  		h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost)
   652  		h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral)
   653  		h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue)
   654  		h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue)
   655  		h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue)
   656  		h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral)
   657  
   658  		// Add the obligation statistics as actual income.
   659  		h.financialMetrics.ContractCompensation = h.financialMetrics.ContractCompensation.Add(so.ContractCost)
   660  		h.financialMetrics.StorageRevenue = h.financialMetrics.StorageRevenue.Add(so.PotentialStorageRevenue)
   661  		h.financialMetrics.DownloadBandwidthRevenue = h.financialMetrics.DownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue)
   662  		h.financialMetrics.UploadBandwidthRevenue = h.financialMetrics.UploadBandwidthRevenue.Add(so.PotentialUploadRevenue)
   663  	}
   664  	if sos == obligationFailed {
   665  		// Remove the obligation statistics as potential risk and income.
   666  		h.log.Printf("Missed storage proof. Revenue would have been %v.\n", so.ContractCost.Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue))
   667  		h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost)
   668  		h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral)
   669  		h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue)
   670  		h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue)
   671  		h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue)
   672  		h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral)
   673  
   674  		// Add the obligation statistics as loss.
   675  		h.financialMetrics.LostStorageCollateral = h.financialMetrics.LostStorageCollateral.Add(so.RiskedCollateral)
   676  		h.financialMetrics.LostRevenue = h.financialMetrics.LostRevenue.Add(so.ContractCost).Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue)
   677  	}
   678  
   679  	// Update the storage obligation to be finalized but still in-database. The
   680  	// obligation status is updated so that the user can see how the obligation
   681  	// ended up, and the sector roots are removed because they are large
   682  	// objects with little purpose once storage proofs are no longer needed.
   683  	h.financialMetrics.ContractCount--
   684  	so.ObligationStatus = sos
   685  	so.SectorRoots = nil
   686  	return h.db.Update(func(tx *bolt.Tx) error {
   687  		return putStorageObligation(tx, so)
   688  	})
   689  }
   690  
   691  func (h *Host) resetFinancialMetrics() error {
   692  	h.mu.RLock()
   693  	defer h.mu.RUnlock()
   694  	// Initialize new values for the host financial metrics.
   695  	fm := modules.HostFinancialMetrics{}
   696  	err := h.db.View(func(tx *bolt.Tx) error {
   697  		c := tx.Bucket(bucketStorageObligations).Cursor()
   698  		for k, v := c.First(); k != nil; k, v = c.Next() {
   699  			var so storageObligation
   700  			if err := json.Unmarshal(v, &so); err != nil {
   701  				return build.ExtendErr("unable to unmarshal storage obligation:", err)
   702  			}
   703  			// Transaction fees are always added.
   704  			fm.TransactionFeeExpenses = fm.TransactionFeeExpenses.Add(so.TransactionFeesAdded)
   705  			// Update the other financial values based on the obligation status.
   706  			if so.ObligationStatus == obligationUnresolved {
   707  				fm.ContractCount++
   708  				fm.PotentialContractCompensation = fm.PotentialContractCompensation.Add(so.ContractCost)
   709  				fm.LockedStorageCollateral = fm.LockedStorageCollateral.Add(so.LockedCollateral)
   710  				fm.PotentialStorageRevenue = fm.PotentialStorageRevenue.Add(so.PotentialStorageRevenue)
   711  				fm.RiskedStorageCollateral = fm.RiskedStorageCollateral.Add(so.RiskedCollateral)
   712  				fm.PotentialDownloadBandwidthRevenue = fm.PotentialDownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue)
   713  				fm.PotentialUploadBandwidthRevenue = fm.PotentialUploadBandwidthRevenue.Add(so.PotentialUploadRevenue)
   714  			}
   715  			if so.ObligationStatus == obligationSucceeded {
   716  				fm.ContractCompensation = fm.ContractCompensation.Add(so.ContractCost)
   717  				fm.StorageRevenue = fm.StorageRevenue.Add(so.PotentialStorageRevenue)
   718  				fm.DownloadBandwidthRevenue = fm.DownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue)
   719  				fm.UploadBandwidthRevenue = fm.UploadBandwidthRevenue.Add(so.PotentialUploadRevenue)
   720  			}
   721  			if so.ObligationStatus == obligationFailed {
   722  				// If there was no risked collateral for the failed obligation, we don't
   723  				// update anything since no revenues were lost. Only the contract compensation
   724  				// and transaction fees are added.
   725  				fm.ContractCompensation = fm.ContractCompensation.Add(so.ContractCost)
   726  				if !so.RiskedCollateral.IsZero() {
   727  					// Storage obligation failed with risked collateral.
   728  					fm.LostRevenue = fm.LostRevenue.Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue)
   729  					fm.LostStorageCollateral = fm.LostStorageCollateral.Add(so.RiskedCollateral)
   730  				}
   731  			}
   732  
   733  		}
   734  		return nil
   735  	})
   736  	if err != nil {
   737  		h.log.Println(build.ExtendErr("unable to reset host financial metrics:", err))
   738  		return err
   739  	}
   740  	h.financialMetrics = fm
   741  	return nil
   742  }
   743  
   744  // threadedHandleActionItem will look at a storage obligation and determine
   745  // which action is necessary for the storage obligation to succeed.
   746  func (h *Host) threadedHandleActionItem(soid types.FileContractID) {
   747  	err := h.tg.Add()
   748  	if err != nil {
   749  		return
   750  	}
   751  	defer h.tg.Done()
   752  
   753  	// Lock the storage obligation in question.
   754  	h.managedLockStorageObligation(soid)
   755  	defer func() {
   756  		h.managedUnlockStorageObligation(soid)
   757  	}()
   758  
   759  	// Fetch the storage obligation associated with the storage obligation id.
   760  	var so storageObligation
   761  	h.mu.RLock()
   762  	blockHeight := h.blockHeight
   763  	err = h.db.View(func(tx *bolt.Tx) error {
   764  		so, err = getStorageObligation(tx, soid)
   765  		return err
   766  	})
   767  	h.mu.RUnlock()
   768  	if err != nil {
   769  		h.log.Println("Could not get storage obligation:", err)
   770  		return
   771  	}
   772  
   773  	// Check whether the storage obligation has already been completed.
   774  	if so.ObligationStatus != obligationUnresolved {
   775  		// Storage obligation has already been completed, skip action item.
   776  		return
   777  	}
   778  
   779  	// Check whether the file contract has been seen. If not, resubmit and
   780  	// queue another action item. Check for death. (signature should have a
   781  	// kill height)
   782  	if !so.OriginConfirmed {
   783  		// Submit the transaction set again, try to get the transaction
   784  		// confirmed.
   785  		err := h.tpool.AcceptTransactionSet(so.OriginTransactionSet)
   786  		if err != nil {
   787  			h.log.Debugln("Could not get origin transaction set accepted", err)
   788  
   789  			// Check if the transaction is invalid with the current consensus set.
   790  			// If so, the transaction is highly unlikely to ever be confirmed, and
   791  			// the storage obligation should be removed. This check should come
   792  			// after logging the errror so that the function can quit.
   793  			//
   794  			// TODO: If the host or tpool is behind consensus, might be difficult
   795  			// to have certainty about the issue. If some but not all of the
   796  			// parents are confirmed, might be some difficulty.
   797  			_, t := err.(modules.ConsensusConflict)
   798  			if t {
   799  				h.log.Println("Consensus conflict on the origin transaction set, id", so.id())
   800  				h.mu.Lock()
   801  				err = h.removeStorageObligation(so, obligationRejected)
   802  				h.mu.Unlock()
   803  				if err != nil {
   804  					h.log.Println("Error removing storage obligation:", err)
   805  				}
   806  				return
   807  			}
   808  		}
   809  
   810  		// Queue another action item to check the status of the transaction.
   811  		h.mu.Lock()
   812  		err = h.queueActionItem(h.blockHeight+resubmissionTimeout, so.id())
   813  		h.mu.Unlock()
   814  		if err != nil {
   815  			h.log.Println("Error queuing action item:", err)
   816  		}
   817  	}
   818  
   819  	// Check if the file contract revision is ready for submission. Check for death.
   820  	if !so.RevisionConfirmed && len(so.RevisionTransactionSet) > 0 && blockHeight >= so.expiration()-revisionSubmissionBuffer {
   821  		// Sanity check - there should be a file contract revision.
   822  		rtsLen := len(so.RevisionTransactionSet)
   823  		if rtsLen < 1 || len(so.RevisionTransactionSet[rtsLen-1].FileContractRevisions) != 1 {
   824  			h.log.Critical("transaction revision marked as unconfirmed, yet there is no transaction revision")
   825  			return
   826  		}
   827  
   828  		// Check if the revision has failed to submit correctly.
   829  		if blockHeight > so.expiration() {
   830  			// TODO: Check this error.
   831  			//
   832  			// TODO: this is not quite right, because a previous revision may
   833  			// be confirmed, and the origin transaction may be confirmed, which
   834  			// would confuse the revenue stuff a bit. Might happen frequently
   835  			// due to the dynamic fee pool.
   836  			h.log.Println("Full time has elapsed, but the revision transaction could not be submitted to consensus, id", so.id())
   837  			h.mu.Lock()
   838  			h.removeStorageObligation(so, obligationRejected)
   839  			h.mu.Unlock()
   840  			return
   841  		}
   842  
   843  		// Queue another action item to check the status of the transaction.
   844  		h.mu.Lock()
   845  		err := h.queueActionItem(blockHeight+resubmissionTimeout, so.id())
   846  		h.mu.Unlock()
   847  		if err != nil {
   848  			h.log.Println("Error queuing action item:", err)
   849  		}
   850  
   851  		// Add a miner fee to the transaction and submit it to the blockchain.
   852  		revisionTxnIndex := len(so.RevisionTransactionSet) - 1
   853  		revisionParents := so.RevisionTransactionSet[:revisionTxnIndex]
   854  		revisionTxn := so.RevisionTransactionSet[revisionTxnIndex]
   855  		builder, err := h.wallet.RegisterTransaction(revisionTxn, revisionParents)
   856  		if err != nil {
   857  			h.log.Println("Error registering transaction:", err)
   858  			return
   859  		}
   860  		_, feeRecommendation := h.tpool.FeeEstimation()
   861  		if so.value().Div64(2).Cmp(feeRecommendation) < 0 {
   862  			// There's no sense submitting the revision if the fee is more than
   863  			// half of the anticipated revenue - fee market went up
   864  			// unexpectedly, and the money that the renter paid to cover the
   865  			// fees is no longer enough.
   866  			builder.Drop()
   867  			return
   868  		}
   869  		txnSize := uint64(len(encoding.MarshalAll(so.RevisionTransactionSet)) + 300)
   870  		requiredFee := feeRecommendation.Mul64(txnSize)
   871  		err = builder.FundSiacoins(requiredFee)
   872  		if err != nil {
   873  			h.log.Println("Error funding transaction fees", err)
   874  			builder.Drop()
   875  		}
   876  		builder.AddMinerFee(requiredFee)
   877  		if err != nil {
   878  			h.log.Println("Error adding miner fees", err)
   879  			builder.Drop()
   880  		}
   881  		feeAddedRevisionTransactionSet, err := builder.Sign(true)
   882  		if err != nil {
   883  			h.log.Println("Error signing transaction", err)
   884  			builder.Drop()
   885  		}
   886  		err = h.tpool.AcceptTransactionSet(feeAddedRevisionTransactionSet)
   887  		if err != nil {
   888  			h.log.Println("Error submitting transaction to transaction pool", err)
   889  			builder.Drop()
   890  		}
   891  		so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee)
   892  		// return
   893  	}
   894  
   895  	// Check whether a storage proof is ready to be provided, and whether it
   896  	// has been accepted. Check for death.
   897  	if !so.ProofConfirmed && blockHeight >= so.expiration()+resubmissionTimeout {
   898  		h.log.Debugln("Host is attempting a storage proof for", so.id())
   899  
   900  		// If the obligation has no sector roots, we can remove the obligation and not
   901  		// submit a storage proof. The host payout for a failed empty contract
   902  		// includes the contract cost and locked collateral.
   903  		if len(so.SectorRoots) == 0 {
   904  			h.log.Debugln("storage proof not submitted for empty contract, id", so.id())
   905  			h.mu.Lock()
   906  			err := h.removeStorageObligation(so, obligationSucceeded)
   907  			h.mu.Unlock()
   908  			if err != nil {
   909  				h.log.Println("Error removing storage obligation:", err)
   910  			}
   911  			return
   912  		}
   913  		// If the window has closed, the host has failed and the obligation can
   914  		// be removed.
   915  		if so.proofDeadline() < blockHeight {
   916  			h.log.Debugln("storage proof not confirmed by deadline, id", so.id())
   917  			h.mu.Lock()
   918  			err := h.removeStorageObligation(so, obligationFailed)
   919  			h.mu.Unlock()
   920  			if err != nil {
   921  				h.log.Println("Error removing storage obligation:", err)
   922  			}
   923  			return
   924  		}
   925  		// Get the index of the segment, and the index of the sector containing
   926  		// the segment.
   927  		segmentIndex, err := h.cs.StorageProofSegment(so.id())
   928  		if err != nil {
   929  			h.log.Debugln("Host got an error when fetching a storage proof segment:", err)
   930  			return
   931  		}
   932  		sectorIndex := segmentIndex / (modules.SectorSize / crypto.SegmentSize)
   933  		// Pull the corresponding sector into memory.
   934  		sectorRoot := so.SectorRoots[sectorIndex]
   935  		sectorBytes, err := h.ReadSector(sectorRoot)
   936  		if err != nil {
   937  			h.log.Debugln(err)
   938  			return
   939  		}
   940  
   941  		// Build the storage proof for just the sector.
   942  		sectorSegment := segmentIndex % (modules.SectorSize / crypto.SegmentSize)
   943  		base, cachedHashSet := crypto.MerkleProof(sectorBytes, sectorSegment)
   944  
   945  		// Using the sector, build a cached root.
   946  		log2SectorSize := uint64(0)
   947  		for 1<<log2SectorSize < (modules.SectorSize / crypto.SegmentSize) {
   948  			log2SectorSize++
   949  		}
   950  		ct := crypto.NewCachedTree(log2SectorSize)
   951  		ct.SetIndex(segmentIndex)
   952  		for _, root := range so.SectorRoots {
   953  			ct.Push(root)
   954  		}
   955  		hashSet := ct.Prove(base, cachedHashSet)
   956  		sp := types.StorageProof{
   957  			ParentID: so.id(),
   958  			HashSet:  hashSet,
   959  		}
   960  		copy(sp.Segment[:], base)
   961  
   962  		// Create and build the transaction with the storage proof.
   963  		builder, err := h.wallet.StartTransaction()
   964  		if err != nil {
   965  			h.log.Println("Failed to start transaction:", err)
   966  			return
   967  		}
   968  		_, feeRecommendation := h.tpool.FeeEstimation()
   969  		if so.value().Cmp(feeRecommendation) < 0 {
   970  			// There's no sense submitting the storage proof if the fee is more
   971  			// than the anticipated revenue.
   972  			h.log.Debugln("Host not submitting storage proof due to a value that does not sufficiently exceed the fee cost")
   973  			builder.Drop()
   974  			return
   975  		}
   976  		txnSize := uint64(len(encoding.Marshal(sp)) + 300)
   977  		requiredFee := feeRecommendation.Mul64(txnSize)
   978  		err = builder.FundSiacoins(requiredFee)
   979  		if err != nil {
   980  			h.log.Println("Host error when funding a storage proof transaction fee:", err)
   981  			builder.Drop()
   982  			return
   983  		}
   984  		builder.AddMinerFee(requiredFee)
   985  		builder.AddStorageProof(sp)
   986  		storageProofSet, err := builder.Sign(true)
   987  		if err != nil {
   988  			h.log.Println("Host error when signing the storage proof transaction:", err)
   989  			builder.Drop()
   990  			return
   991  		}
   992  		err = h.tpool.AcceptTransactionSet(storageProofSet)
   993  		if err != nil {
   994  			h.log.Println("Host unable to submit storage proof transaction to transaction pool:", err)
   995  			builder.Drop()
   996  			return
   997  		}
   998  		so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee)
   999  
  1000  		// Queue another action item to check whether the storage proof
  1001  		// got confirmed.
  1002  		h.mu.Lock()
  1003  		err = h.queueActionItem(so.proofDeadline(), so.id())
  1004  		h.mu.Unlock()
  1005  		if err != nil {
  1006  			h.log.Println("Error queuing action item:", err)
  1007  		}
  1008  	}
  1009  
  1010  	// Save the storage obligation to account for any fee changes.
  1011  	err = h.db.Update(func(tx *bolt.Tx) error {
  1012  		soBytes, err := json.Marshal(so)
  1013  		if err != nil {
  1014  			return err
  1015  		}
  1016  		return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes)
  1017  	})
  1018  	if err != nil {
  1019  		h.log.Println("Error updating the storage obligations", err)
  1020  	}
  1021  
  1022  	// Check if all items have succeeded with the required confirmations. Report
  1023  	// success, delete the obligation.
  1024  	if so.ProofConfirmed && blockHeight >= so.proofDeadline() {
  1025  		h.log.Println("file contract complete, id", so.id())
  1026  		h.mu.Lock()
  1027  		h.removeStorageObligation(so, obligationSucceeded)
  1028  		h.mu.Unlock()
  1029  	}
  1030  }
  1031  
  1032  // StorageObligations fetches the set of storage obligations in the host and
  1033  // returns metadata on them.
  1034  func (h *Host) StorageObligations() (sos []modules.StorageObligation) {
  1035  	h.mu.RLock()
  1036  	defer h.mu.RUnlock()
  1037  
  1038  	err := h.db.View(func(tx *bolt.Tx) error {
  1039  		b := tx.Bucket(bucketStorageObligations)
  1040  		err := b.ForEach(func(idBytes, soBytes []byte) error {
  1041  			var so storageObligation
  1042  			err := json.Unmarshal(soBytes, &so)
  1043  			if err != nil {
  1044  				return build.ExtendErr("unable to unmarshal storage obligation:", err)
  1045  			}
  1046  			mso := modules.StorageObligation{
  1047  				ContractCost:             so.ContractCost,
  1048  				DataSize:                 so.fileSize(),
  1049  				LockedCollateral:         so.LockedCollateral,
  1050  				ObligationId:             so.id(),
  1051  				PotentialDownloadRevenue: so.PotentialDownloadRevenue,
  1052  				PotentialStorageRevenue:  so.PotentialStorageRevenue,
  1053  				PotentialUploadRevenue:   so.PotentialUploadRevenue,
  1054  				RiskedCollateral:         so.RiskedCollateral,
  1055  				SectorRootsCount:         uint64(len(so.SectorRoots)),
  1056  				TransactionFeesAdded:     so.TransactionFeesAdded,
  1057  				TransactionID:            so.transactionID(),
  1058  
  1059  				ExpirationHeight:  so.expiration(),
  1060  				NegotiationHeight: so.NegotiationHeight,
  1061  				ProofDeadLine:     so.proofDeadline(),
  1062  
  1063  				ObligationStatus:    so.ObligationStatus.String(),
  1064  				OriginConfirmed:     so.OriginConfirmed,
  1065  				ProofConfirmed:      so.ProofConfirmed,
  1066  				ProofConstructed:    so.ProofConstructed,
  1067  				RevisionConfirmed:   so.RevisionConfirmed,
  1068  				RevisionConstructed: so.RevisionConstructed,
  1069  			}
  1070  			sos = append(sos, mso)
  1071  			return nil
  1072  		})
  1073  		if err != nil {
  1074  			return build.ExtendErr("ForEach failed to get next storage obligation:", err)
  1075  		}
  1076  		return nil
  1077  	})
  1078  	if err != nil {
  1079  		h.log.Println(build.ExtendErr("database failed to provide storage obligations:", err))
  1080  	}
  1081  
  1082  	return sos
  1083  }