github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/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/NebulousLabs/bolt"
    42  )
    43  
    44  const (
    45  	obligationConfused  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  	// errObligationLocked is returned when a storage obligation is being put
    93  	// under lock, but is already locked.
    94  	errObligationLocked = errors.New("storage obligation is locked, and is unavailable for editing")
    95  
    96  	// errObligationUnlocked is returned when a storage obligation is being
    97  	// removed from lock, but is already unlocked.
    98  	errObligationUnlocked = errors.New("storage obligation is unlocked, and should not be getting unlocked")
    99  
   100  	// errNoBuffer is returned if there is an attempted storage obligation that
   101  	// needs to have the storage proof submitted in less than
   102  	// revisionSubmissionBuffer blocks.
   103  	errNoBuffer = errors.New("file contract rejected because storage proof window is too close")
   104  
   105  	// errNoStorageObligation is returned if the requested storage obligation
   106  	// is not found in the database.
   107  	errNoStorageObligation = errors.New("storage obligation not found in database")
   108  )
   109  
   110  type storageObligationStatus int
   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  	OriginTransactionSet   []types.Transaction
   135  	RevisionTransactionSet []types.Transaction
   136  
   137  	// Variables indicating whether the critical transactions in a storage
   138  	// obligation have been confirmed on the blockchain.
   139  	OriginConfirmed   bool
   140  	RevisionConfirmed bool
   141  	ProofConfirmed    bool
   142  }
   143  
   144  // getStorageObligation fetches a storage obligation from the database tx.
   145  func getStorageObligation(tx *bolt.Tx, soid types.FileContractID) (so storageObligation, err error) {
   146  	soBytes := tx.Bucket(bucketStorageObligations).Get(soid[:])
   147  	if soBytes == nil {
   148  		return storageObligation{}, errNoStorageObligation
   149  	}
   150  	err = json.Unmarshal(soBytes, &so)
   151  	if err != nil {
   152  		return storageObligation{}, err
   153  	}
   154  	return so, nil
   155  }
   156  
   157  // putStorageObligation places a storage obligation into the database,
   158  // overwriting the existing storage obligation if there is one.
   159  func putStorageObligation(tx *bolt.Tx, so storageObligation) error {
   160  	soBytes, err := json.Marshal(so)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	soid := so.id()
   165  	return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes)
   166  }
   167  
   168  // expiration returns the height at which the storage obligation expires.
   169  func (so *storageObligation) expiration() types.BlockHeight {
   170  	if len(so.RevisionTransactionSet) > 0 {
   171  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewWindowStart
   172  	}
   173  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].WindowStart
   174  }
   175  
   176  // fileSize returns the size of the data protected by the obligation.
   177  func (so *storageObligation) fileSize() uint64 {
   178  	if len(so.RevisionTransactionSet) > 0 {
   179  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewFileSize
   180  	}
   181  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].FileSize
   182  }
   183  
   184  // id returns the id of the storage obligation, which is definied by the file
   185  // contract id of the file contract that governs the storage contract.
   186  func (so *storageObligation) id() types.FileContractID {
   187  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContractID(0)
   188  }
   189  
   190  // isSane checks that required assumptions about the storage obligation are
   191  // correct.
   192  func (so *storageObligation) isSane() error {
   193  	// There should be an origin transaction set.
   194  	if len(so.OriginTransactionSet) == 0 {
   195  		build.Critical("origin transaction set is empty")
   196  		return errInsaneOriginSetSize
   197  	}
   198  
   199  	// The final transaction of the origin transaction set should have one file
   200  	// contract.
   201  	final := len(so.OriginTransactionSet) - 1
   202  	fcCount := len(so.OriginTransactionSet[final].FileContracts)
   203  	if fcCount != 1 {
   204  		build.Critical("wrong number of file contracts associated with storage obligation:", fcCount)
   205  		return errInsaneOriginSetFileContract
   206  	}
   207  
   208  	// The file contract in the final transaction of the origin transaction set
   209  	// should have two valid proof outputs and two missed proof outputs.
   210  	lenVPOs := len(so.OriginTransactionSet[final].FileContracts[0].ValidProofOutputs)
   211  	lenMPOs := len(so.OriginTransactionSet[final].FileContracts[0].MissedProofOutputs)
   212  	if lenVPOs != 2 || lenMPOs != 2 {
   213  		build.Critical("file contract has wrong number of VPOs and MPOs, expecting 2 each:", lenVPOs, lenMPOs)
   214  		return errInsaneFileContractOutputCounts
   215  	}
   216  
   217  	// If there is a revision transaction set, there should be one file
   218  	// contract revision in the final transaction.
   219  	if len(so.RevisionTransactionSet) > 0 {
   220  		final = len(so.OriginTransactionSet) - 1
   221  		fcrCount := len(so.OriginTransactionSet[final].FileContractRevisions)
   222  		if fcrCount != 1 {
   223  			build.Critical("wrong number of file contract revisions in final transaction of revision transaction set:", fcrCount)
   224  			return errInsaneRevisionSetRevisionCount
   225  		}
   226  
   227  		// The file contract revision in the final transaction of the revision
   228  		// transaction set should have two valid proof outputs and two missed
   229  		// proof outputs.
   230  		lenVPOs = len(so.RevisionTransactionSet[final].FileContractRevisions[0].NewValidProofOutputs)
   231  		lenMPOs = len(so.RevisionTransactionSet[final].FileContractRevisions[0].NewMissedProofOutputs)
   232  		if lenVPOs != 2 || lenMPOs != 2 {
   233  			build.Critical("file contract has wrong number of VPOs and MPOs, expecting 2 each:", lenVPOs, lenMPOs)
   234  			return errInsaneFileContractRevisionOutputCounts
   235  		}
   236  	}
   237  	return nil
   238  }
   239  
   240  // merkleRoot returns the file merkle root of a storage obligation.
   241  func (so *storageObligation) merkleRoot() crypto.Hash {
   242  	if len(so.RevisionTransactionSet) > 0 {
   243  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewFileMerkleRoot
   244  	}
   245  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].FileMerkleRoot
   246  }
   247  
   248  // payous returns the set of valid payouts and missed payouts that represent
   249  // the latest revision for the storage obligation.
   250  func (so *storageObligation) payouts() (valid []types.SiacoinOutput, missed []types.SiacoinOutput) {
   251  	valid = make([]types.SiacoinOutput, 2)
   252  	missed = make([]types.SiacoinOutput, 2)
   253  	if len(so.RevisionTransactionSet) > 0 {
   254  		copy(valid, so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewValidProofOutputs)
   255  		copy(missed, so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewMissedProofOutputs)
   256  		return
   257  	}
   258  	copy(valid, so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].ValidProofOutputs)
   259  	copy(missed, so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].MissedProofOutputs)
   260  	return
   261  }
   262  
   263  // proofDeadline returns the height by which the storage proof must be
   264  // submitted.
   265  func (so *storageObligation) proofDeadline() types.BlockHeight {
   266  	if len(so.RevisionTransactionSet) > 0 {
   267  		return so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0].NewWindowEnd
   268  	}
   269  	return so.OriginTransactionSet[len(so.OriginTransactionSet)-1].FileContracts[0].WindowEnd
   270  }
   271  
   272  // value returns the value of fulfilling the storage obligation to the host.
   273  func (so *storageObligation) value() types.Currency {
   274  	return so.ContractCost.Add(so.PotentialDownloadRevenue).Add(so.PotentialStorageRevenue).Add(so.PotentialUploadRevenue).Add(so.RiskedCollateral)
   275  }
   276  
   277  // queueActionItem adds an action item to the host at the input height so that
   278  // the host knows to perform maintenance on the associated storage obligation
   279  // when that height is reached.
   280  func (h *Host) queueActionItem(height types.BlockHeight, id types.FileContractID) error {
   281  	// Sanity check - action item should be at a higher height than the current
   282  	// block height.
   283  	if height <= h.blockHeight {
   284  		h.log.Critical("action item queued improperly")
   285  	}
   286  	return h.db.Update(func(tx *bolt.Tx) error {
   287  		// Translate the height into a byte slice.
   288  		heightBytes := make([]byte, 8)
   289  		binary.BigEndian.PutUint64(heightBytes, uint64(height))
   290  
   291  		// Get the list of action items already at this height and extend it.
   292  		bai := tx.Bucket(bucketActionItems)
   293  		existingItems := bai.Get(heightBytes)
   294  		var extendedItems = make([]byte, len(existingItems), len(existingItems)+len(id[:]))
   295  		copy(extendedItems, existingItems)
   296  		extendedItems = append(extendedItems, id[:]...)
   297  		return bai.Put(heightBytes, extendedItems)
   298  	})
   299  }
   300  
   301  // addStorageObligation adds a storage obligation to the host. Because this
   302  // operation can return errors, the transactions should not be submitted to the
   303  // blockchain until after this function has indicated success. All of the
   304  // sectors that are present in the storage obligation should already be on
   305  // disk, which means that addStorageObligation should be exclusively called
   306  // when creating a new, empty file contract or when renewing an existing file
   307  // contract.
   308  func (h *Host) addStorageObligation(so *storageObligation) error {
   309  	// Sanity check - obligation should be under lock while being added.
   310  	soid := so.id()
   311  	_, exists := h.lockedStorageObligations[soid]
   312  	if !exists {
   313  		h.log.Critical("addStorageObligation called with an obligation that is not locked")
   314  	}
   315  	// Sanity check - There needs to be enough time left on the file contract
   316  	// for the host to safely submit the file contract revision.
   317  	if h.blockHeight+revisionSubmissionBuffer >= so.expiration() {
   318  		h.log.Critical("submission window was not verified before trying to submit a storage obligation")
   319  		return errNoBuffer
   320  	}
   321  	// Sanity check - the resubmission timeout needs to be smaller than storage
   322  	// proof window.
   323  	if so.expiration()+resubmissionTimeout >= so.proofDeadline() {
   324  		h.log.Critical("host is misconfigured - the storage proof window needs to be long enough to resubmit if needed")
   325  		return errors.New("fill me in")
   326  	}
   327  
   328  	// Add the storage obligation information to the database.
   329  	err := h.db.Update(func(tx *bolt.Tx) error {
   330  		// Sanity check - a storage obligation using the same file contract id
   331  		// should not already exist. This situation can happen if the
   332  		// transaction pool ejects a file contract and then a new one is
   333  		// created. Though the file contract will have the same terms, some
   334  		// other conditions might cause problems. The check for duplicate file
   335  		// contract ids should happen during the negotiation phase, and not
   336  		// during the 'addStorageObligation' phase.
   337  		bso := tx.Bucket(bucketStorageObligations)
   338  		soBytes := bso.Get(soid[:])
   339  		if soBytes != nil {
   340  			h.log.Critical("host already has a save storage obligation for this file contract")
   341  			return errDuplicateStorageObligation
   342  		}
   343  
   344  		// If the storage obligation already has sectors, it means that the
   345  		// file contract is being renewed, and that the sector should be
   346  		// re-added with a new expriation height. If there is an error at any
   347  		// point, all of the sectors should be removed.
   348  		for i, root := range so.SectorRoots {
   349  			err := h.AddSector(root, so.expiration(), make([]byte, modules.SectorSize))
   350  			if err != nil {
   351  				// Remove all of the sectors that got added and return an error.
   352  				for j := 0; j < i; j++ {
   353  					removeErr := h.RemoveSector(so.SectorRoots[j], so.expiration())
   354  					if removeErr != nil {
   355  						h.log.Println(removeErr)
   356  					}
   357  				}
   358  				return err
   359  			}
   360  		}
   361  
   362  		// Add the storage obligation to the database.
   363  		soBytes, err := json.Marshal(*so)
   364  		if err != nil {
   365  			return err
   366  		}
   367  		return bso.Put(soid[:], soBytes)
   368  	})
   369  	if err != nil {
   370  		return err
   371  	}
   372  
   373  	// Update the host financial metrics with regards to this storage
   374  	// obligation.
   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  
   383  	// Set an action item that will have the host verify that the file contract
   384  	// has been submitted to the blockchain, then another to submit the file
   385  	// contract revision to the blockchain, and another to submit the storage
   386  	// proof.
   387  	err0 := h.tpool.AcceptTransactionSet(so.OriginTransactionSet)
   388  	// The file contract was already submitted to the blockchain, need to check
   389  	// after the resubmission timeout that it was submitted successfully.
   390  	err1 := h.queueActionItem(h.blockHeight+resubmissionTimeout, soid)
   391  	// Queue an action item to submit the file contract revision - if there is
   392  	// never a file contract revision, the handling of this action item will be
   393  	// a no-op.
   394  	err2 := h.queueActionItem(so.expiration()-revisionSubmissionBuffer, soid)
   395  	// The storage proof should be submitted
   396  	err3 := h.queueActionItem(so.expiration()+resubmissionTimeout, soid)
   397  	err = composeErrors(err0, err1, err2, err3)
   398  	if err != nil {
   399  		return composeErrors(err, h.removeStorageObligation(so, obligationRejected))
   400  	}
   401  	return nil
   402  }
   403  
   404  // lockStorageObligation puts a storage obligation under lock in the host.
   405  func (h *Host) lockStorageObligation(so *storageObligation) error {
   406  	_, exists := h.lockedStorageObligations[so.id()]
   407  	if exists {
   408  		return errObligationLocked
   409  	}
   410  	h.lockedStorageObligations[so.id()] = struct{}{}
   411  	return nil
   412  }
   413  
   414  // modifyStorageObligation will take an updated storage obligation along with a
   415  // list of sector changes and update the database to account for all of it. The
   416  // sector modifications are only used to update the sector database, they will
   417  // not be used to modify the storage obligation (most importantly, this means
   418  // that sectorRoots needs to be updated by the calling function). Virtual
   419  // sectors will be removed the number of times that they are listed, to remove
   420  // multiple instances of the same virtual sector, the virtural sector will need
   421  // to appear in 'sectorsRemoved' multiple times. Same with 'sectorsGained'.
   422  func (h *Host) modifyStorageObligation(so *storageObligation, sectorsRemoved []crypto.Hash, sectorsGained []crypto.Hash, gainedSectorData [][]byte) error {
   423  	// Sanity check - obligation should be under lock while being modified.
   424  	soid := so.id()
   425  	_, exists := h.lockedStorageObligations[soid]
   426  	if !exists {
   427  		h.log.Critical("modifyStorageObligation called with an obligation that is not locked")
   428  	}
   429  	// Sanity check - there needs to be enough time to submit the file contract
   430  	// revision to the blockchain.
   431  	if so.expiration()-revisionSubmissionBuffer <= h.blockHeight {
   432  		h.log.Critical("revision submission window was not verified before trying to modify a storage obligation")
   433  		return errNoBuffer
   434  	}
   435  	// Sanity check - sectorsGained and gainedSectorData need to have the same length.
   436  	if len(sectorsGained) != len(gainedSectorData) {
   437  		h.log.Critical("modifying a revision with garbage sector data", len(sectorsGained), len(gainedSectorData))
   438  		return errInsaneStorageObligationRevision
   439  	}
   440  	// Sanity check - all of the sector data should be modules.SectorSize
   441  	for _, data := range gainedSectorData {
   442  		if uint64(len(data)) != modules.SectorSize {
   443  			h.log.Critical("modifying a revision with garbase sector sizes", len(data))
   444  			return errInsaneStorageObligationRevision
   445  		}
   446  	}
   447  
   448  	// Note, for safe error handling, the operation order should be: add
   449  	// sectors, update database, remove sectors. If the adding or update fails,
   450  	// the added sectors should be removed and the storage obligation shoud be
   451  	// considered invalid. If the removing fails, this is okay, it's ignored
   452  	// and left to consistency checks and user actions to fix (will reduce host
   453  	// capacity, but will not inhibit the host's ability to submit storage
   454  	// proofs)
   455  	var i int
   456  	var err error
   457  	for i = range sectorsGained {
   458  		err = h.AddSector(sectorsGained[i], so.expiration(), gainedSectorData[i])
   459  		if err != nil {
   460  			break
   461  		}
   462  	}
   463  	if err != nil {
   464  		// Because there was an error, all of the sectors that got added need
   465  		// to be reverted.
   466  		for j := 0; j < i; j++ {
   467  			// Error is not checked because there's nothing useful that can be
   468  			// done about an error.
   469  			_ = h.RemoveSector(sectorsGained[j], so.expiration())
   470  		}
   471  		return err
   472  	}
   473  	// Update the database to contain the new storage obligation.
   474  	var oldSO storageObligation
   475  	err = h.db.Update(func(tx *bolt.Tx) error {
   476  		// Get the old storage obligation as a reference to know how to upate
   477  		// the host financial stats.
   478  		oldSO, err = getStorageObligation(tx, soid)
   479  		if err != nil {
   480  			return err
   481  		}
   482  
   483  		// Store the new storage obligation to replace the old one.
   484  		return putStorageObligation(tx, *so)
   485  	})
   486  	if err != nil {
   487  		// Because there was an error, all of the sectors that got added need
   488  		// to be reverted.
   489  		for i := range sectorsGained {
   490  			// Error is not checked because there's nothing useful that can be
   491  			// done about an error.
   492  			_ = h.RemoveSector(sectorsGained[i], so.expiration())
   493  		}
   494  		return err
   495  	}
   496  	// Call removeSector for all of the sectors that have been removed.
   497  	for k := range sectorsRemoved {
   498  		// Error is not checkeed because there's nothing useful that can be
   499  		// done about an error. Failing to remove a sector is not a terrible
   500  		// place to be, especially if the host can run consistency checks.
   501  		_ = h.RemoveSector(sectorsRemoved[k], so.expiration())
   502  	}
   503  
   504  	// Update the financial information for the storage obligation - remove the
   505  	// old values.
   506  	h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(oldSO.ContractCost)
   507  	h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(oldSO.LockedCollateral)
   508  	h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(oldSO.PotentialStorageRevenue)
   509  	h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(oldSO.PotentialDownloadRevenue)
   510  	h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(oldSO.PotentialUploadRevenue)
   511  	h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(oldSO.RiskedCollateral)
   512  	h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Sub(oldSO.TransactionFeesAdded)
   513  
   514  	// Update the financial information for the storage obligation - apply the
   515  	// new values.
   516  	h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Add(so.ContractCost)
   517  	h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Add(so.LockedCollateral)
   518  	h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Add(so.PotentialStorageRevenue)
   519  	h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue)
   520  	h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Add(so.PotentialUploadRevenue)
   521  	h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Add(so.RiskedCollateral)
   522  	h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Add(so.TransactionFeesAdded)
   523  	return nil
   524  }
   525  
   526  // removeStorageObligation will remove a storage obligation from the host,
   527  // either due to failure or success.
   528  func (h *Host) removeStorageObligation(so *storageObligation, sos storageObligationStatus) error {
   529  	// Call removeSector for every sector in the storage obligation.
   530  	for _, root := range so.SectorRoots {
   531  		// Error is not checked, we want to call remove on every sector even if
   532  		// there are problems - disk health information will be updated.
   533  		_ = h.RemoveSector(root, so.expiration())
   534  	}
   535  
   536  	// Update the host revenue metrics based on the status of the obligation.
   537  	if sos == obligationConfused {
   538  		h.log.Critical("storage obligation confused!")
   539  	}
   540  	if sos == obligationRejected {
   541  		// Remove the obligation statistics as potential risk and income.
   542  		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))
   543  		h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost)
   544  		h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral)
   545  		h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue)
   546  		h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue)
   547  		h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue)
   548  		h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral)
   549  		h.financialMetrics.TransactionFeeExpenses = h.financialMetrics.TransactionFeeExpenses.Sub(so.TransactionFeesAdded)
   550  	}
   551  	if sos == obligationSucceeded {
   552  		// Remove the obligation statistics as potential risk and income.
   553  		h.log.Printf("Succesfully submitted a storage proof. Revenue is %v.\n", h.financialMetrics.PotentialContractCompensation.Add(h.financialMetrics.PotentialStorageRevenue).Add(h.financialMetrics.PotentialDownloadBandwidthRevenue).Add(h.financialMetrics.PotentialUploadBandwidthRevenue))
   554  		h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost)
   555  		h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral)
   556  		h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue)
   557  		h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue)
   558  		h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue)
   559  		h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral)
   560  
   561  		// Add the obligation statistics as actual income.
   562  		h.financialMetrics.ContractCompensation = h.financialMetrics.ContractCompensation.Add(so.ContractCost)
   563  		h.financialMetrics.StorageRevenue = h.financialMetrics.StorageRevenue.Add(so.PotentialStorageRevenue)
   564  		h.financialMetrics.DownloadBandwidthRevenue = h.financialMetrics.DownloadBandwidthRevenue.Add(so.PotentialDownloadRevenue)
   565  		h.financialMetrics.UploadBandwidthRevenue = h.financialMetrics.UploadBandwidthRevenue.Add(so.PotentialUploadRevenue)
   566  	}
   567  	if sos == obligationFailed {
   568  		// Remove the obligation statistics as potential risk and income.
   569  		h.log.Printf("Missed storage proof. Revenue would have been %v.\n", h.financialMetrics.PotentialContractCompensation.Add(h.financialMetrics.PotentialStorageRevenue).Add(h.financialMetrics.PotentialDownloadBandwidthRevenue).Add(h.financialMetrics.PotentialUploadBandwidthRevenue))
   570  		h.financialMetrics.PotentialContractCompensation = h.financialMetrics.PotentialContractCompensation.Sub(so.ContractCost)
   571  		h.financialMetrics.LockedStorageCollateral = h.financialMetrics.LockedStorageCollateral.Sub(so.LockedCollateral)
   572  		h.financialMetrics.PotentialStorageRevenue = h.financialMetrics.PotentialStorageRevenue.Sub(so.PotentialStorageRevenue)
   573  		h.financialMetrics.PotentialDownloadBandwidthRevenue = h.financialMetrics.PotentialDownloadBandwidthRevenue.Sub(so.PotentialDownloadRevenue)
   574  		h.financialMetrics.PotentialUploadBandwidthRevenue = h.financialMetrics.PotentialUploadBandwidthRevenue.Sub(so.PotentialUploadRevenue)
   575  		h.financialMetrics.RiskedStorageCollateral = h.financialMetrics.RiskedStorageCollateral.Sub(so.RiskedCollateral)
   576  
   577  		// Add the obligation statistics as loss.
   578  		h.financialMetrics.LostStorageCollateral = h.financialMetrics.LostStorageCollateral.Add(so.RiskedCollateral)
   579  		h.financialMetrics.LostRevenue = h.financialMetrics.LostRevenue.Add(so.ContractCost).Add(so.PotentialStorageRevenue).Add(so.PotentialDownloadRevenue).Add(so.PotentialUploadRevenue)
   580  	}
   581  
   582  	// Delete the storage obligation from the database.
   583  	return h.db.Update(func(tx *bolt.Tx) error {
   584  		soid := so.id()
   585  		return tx.Bucket(bucketStorageObligations).Delete(soid[:])
   586  	})
   587  }
   588  
   589  // handleActionItem will look at a storage obligation and determine which
   590  // action is necessary for the storage obligation to succeed.
   591  func (h *Host) handleActionItem(so *storageObligation) {
   592  	// Check whether the file contract has been seen. If not, resubmit and
   593  	// queue another action item. Check for death. (signature should have a
   594  	// kill height)
   595  	if !so.OriginConfirmed {
   596  		// Submit the transaction set again, try to get the transaction
   597  		// confirmed.
   598  		err := h.tpool.AcceptTransactionSet(so.OriginTransactionSet)
   599  		if err != nil {
   600  			h.log.Debugln("Could not get origin transaction set accepted", err)
   601  
   602  			// Check if the transaction is invalid with the current consensus set.
   603  			// If so, the transaction is highly unlikely to ever be confirmed, and
   604  			// the storage obligation should be removed. This check should come
   605  			// after logging the errror so that the function can quit.
   606  			//
   607  			// TODO: If the host or tpool is behind consensus, might be difficult
   608  			// to have certainty about the issue. If some but not all of the
   609  			// parents are confirmed, might be some difficulty.
   610  			_, t := err.(modules.ConsensusConflict)
   611  			if t {
   612  				h.log.Debugln("Consensus conflict on the origin transaction set")
   613  				err = h.removeStorageObligation(so, obligationRejected)
   614  				if err != nil {
   615  					h.log.Println("Error removing storage obligation:", err)
   616  				}
   617  				return
   618  			}
   619  		}
   620  
   621  		// Queue another action item to check the status of the transaction.
   622  		err = h.queueActionItem(h.blockHeight+resubmissionTimeout, so.id())
   623  		if err != nil {
   624  			h.log.Println("Error queuing action item:", err)
   625  		}
   626  	}
   627  
   628  	// Check if the file contract revision is ready for submission. Check for death.
   629  	if !so.RevisionConfirmed && len(so.RevisionTransactionSet) > 0 && h.blockHeight > so.expiration()-revisionSubmissionBuffer {
   630  		// Sanity check - there should be a file contract revision.
   631  		rtsLen := len(so.RevisionTransactionSet)
   632  		if rtsLen < 1 || len(so.RevisionTransactionSet[rtsLen-1].FileContractRevisions) != 1 {
   633  			h.log.Critical("transaction revision marked as unconfirmed, yet there is no transaction revision")
   634  			return
   635  		}
   636  
   637  		// Check if the revision has failed to submit correctly.
   638  		if h.blockHeight > so.expiration() {
   639  			// TODO: Check this error.
   640  			//
   641  			// TODO: this is not quite right, because a previous revision may
   642  			// be confirmed, and the origin transaction may be confirmed, which
   643  			// would confuse the revenue stuff a bit. Might happen frequently
   644  			// due to the dynamic fee pool.
   645  			h.log.Debugln("Full time has elapsed, but the revision transaction could not be submitted to consensus")
   646  			h.removeStorageObligation(so, obligationRejected)
   647  			return
   648  		}
   649  
   650  		// Queue another action item to check the status of the transaction.
   651  		err := h.queueActionItem(h.blockHeight+resubmissionTimeout, so.id())
   652  		if err != nil {
   653  			h.log.Println("Error queuing action item:", err)
   654  		}
   655  
   656  		// Add a miner fee to the transaction and submit it to the blockchain.
   657  		revisionTxnIndex := len(so.RevisionTransactionSet) - 1
   658  		revisionParents := so.RevisionTransactionSet[:revisionTxnIndex]
   659  		revisionTxn := so.RevisionTransactionSet[revisionTxnIndex]
   660  		builder := h.wallet.RegisterTransaction(revisionTxn, revisionParents)
   661  		feeRecommendation, _ := h.tpool.FeeEstimation()
   662  		if so.value().Div64(2).Cmp(feeRecommendation) < 0 {
   663  			// There's no sense submitting the revision if the fee is more than
   664  			// half of the anticipated revenue - fee market went up
   665  			// unexpectedly, and the money that the renter paid to cover the
   666  			// fees is no longer enough.
   667  			return
   668  		}
   669  		txnSize := uint64(len(encoding.MarshalAll(so.RevisionTransactionSet)) + 300)
   670  		requiredFee := feeRecommendation.Mul64(txnSize)
   671  		err = builder.FundSiacoins(requiredFee)
   672  		if err != nil {
   673  			h.log.Println("Error funding transaction fees", err)
   674  		}
   675  		builder.AddMinerFee(requiredFee)
   676  		if err != nil {
   677  			h.log.Println("Error adding miner fees", err)
   678  		}
   679  		feeAddedRevisionTransactionSet, err := builder.Sign(true)
   680  		if err != nil {
   681  			h.log.Println("Error signing transaction", err)
   682  		}
   683  		err = h.tpool.AcceptTransactionSet(feeAddedRevisionTransactionSet)
   684  		if err != nil {
   685  			h.log.Println("Error submitting transaction to transaction pool", err)
   686  		}
   687  		so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee)
   688  		// return
   689  	}
   690  
   691  	// Check whether a storage proof is ready to be provided, and whether it
   692  	// has been accepted. Check for death.
   693  	if !so.ProofConfirmed && h.blockHeight >= so.expiration()+resubmissionTimeout {
   694  		// If the window has closed, the host has failed and the obligation can
   695  		// be removed.
   696  		if so.proofDeadline() < h.blockHeight || len(so.SectorRoots) == 0 {
   697  			err := h.removeStorageObligation(so, obligationFailed)
   698  			if err != nil {
   699  				h.log.Println("Error removing storage obligation:", err)
   700  			}
   701  			return
   702  		}
   703  
   704  		// Get the index of the segment, and the index of the sector containing
   705  		// the segment.
   706  		segmentIndex, err := h.cs.StorageProofSegment(so.id())
   707  		if err != nil {
   708  			return
   709  		}
   710  		sectorIndex := segmentIndex / (modules.SectorSize / crypto.SegmentSize)
   711  		// Pull the corresponding sector into memory.
   712  		sectorRoot := so.SectorRoots[sectorIndex]
   713  		sectorBytes, err := h.ReadSector(sectorRoot)
   714  		if err != nil {
   715  			return
   716  		}
   717  
   718  		// Build the storage proof for just the sector.
   719  		sectorSegment := segmentIndex % (modules.SectorSize / crypto.SegmentSize)
   720  		base, cachedHashSet := crypto.MerkleProof(sectorBytes, sectorSegment)
   721  
   722  		// Using the sector, build a cached root.
   723  		log2SectorSize := uint64(0)
   724  		for 1<<log2SectorSize < (modules.SectorSize / crypto.SegmentSize) {
   725  			log2SectorSize++
   726  		}
   727  		ct := crypto.NewCachedTree(log2SectorSize)
   728  		ct.SetIndex(segmentIndex)
   729  		for _, root := range so.SectorRoots {
   730  			ct.Push(root)
   731  		}
   732  		hashSet := ct.Prove(base, cachedHashSet)
   733  		sp := types.StorageProof{
   734  			ParentID: so.id(),
   735  			HashSet:  hashSet,
   736  		}
   737  		copy(sp.Segment[:], base)
   738  
   739  		// Create and build the transaction with the storage proof.
   740  		builder := h.wallet.StartTransaction()
   741  		feeRecommendation, _ := h.tpool.FeeEstimation()
   742  		if so.value().Cmp(feeRecommendation) < 0 {
   743  			// There's no sense submitting the storage proof of the fee is more
   744  			// than the anticipated revenue.
   745  			return
   746  		}
   747  		txnSize := uint64(len(encoding.Marshal(sp)) + 300)
   748  		requiredFee := feeRecommendation.Mul64(txnSize)
   749  		err = builder.FundSiacoins(requiredFee)
   750  		if err != nil {
   751  			return
   752  		}
   753  		builder.AddMinerFee(requiredFee)
   754  		builder.AddStorageProof(sp)
   755  		storageProofSet, err := builder.Sign(true)
   756  		if err != nil {
   757  			return
   758  		}
   759  		err = h.tpool.AcceptTransactionSet(storageProofSet)
   760  		if err != nil {
   761  			return
   762  		}
   763  		so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee)
   764  
   765  		// Queue another action item to check whether there the storage proof
   766  		// got confirmed.
   767  		err = h.queueActionItem(so.proofDeadline(), so.id())
   768  		if err != nil {
   769  			h.log.Println("Error queuing action item:", err)
   770  		}
   771  	}
   772  
   773  	// Save the storage obligation to account for any fee changes.
   774  	err := h.db.Update(func(tx *bolt.Tx) error {
   775  		soBytes, err := json.Marshal(*so)
   776  		if err != nil {
   777  			return err
   778  		}
   779  		soid := so.id()
   780  		return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes)
   781  	})
   782  	if err != nil {
   783  		h.log.Println("Error updating the storage obligations", err)
   784  	}
   785  
   786  	// Check if all items have succeeded with the required confirmations. Report
   787  	// success, delete the obligation.
   788  	if so.ProofConfirmed && h.blockHeight >= so.proofDeadline() {
   789  		h.removeStorageObligation(so, obligationSucceeded)
   790  	}
   791  }
   792  
   793  // unlockStorageObligation takes a storage obligation out from under lock in
   794  // the host.
   795  func (h *Host) unlockStorageObligation(so *storageObligation) error {
   796  	_, exists := h.lockedStorageObligations[so.id()]
   797  	if !exists {
   798  		h.log.Critical(errObligationUnlocked)
   799  		return errObligationUnlocked
   800  	}
   801  	delete(h.lockedStorageObligations, so.id())
   802  	return nil
   803  }