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