github.com/nebulouslabs/sia@v1.3.7/modules/renter/contractor/contracts.go (about)

     1  package contractor
     2  
     3  // contracts.go handles forming and renewing contracts for the contractor. This
     4  // includes deciding when new contracts need to be formed, when contracts need
     5  // to be renewed, and if contracts need to be blacklisted.
     6  
     7  import (
     8  	"fmt"
     9  	"math/big"
    10  
    11  	"github.com/NebulousLabs/Sia/build"
    12  	"github.com/NebulousLabs/Sia/modules"
    13  	"github.com/NebulousLabs/Sia/modules/renter/proto"
    14  	"github.com/NebulousLabs/Sia/types"
    15  
    16  	"github.com/NebulousLabs/errors"
    17  )
    18  
    19  var (
    20  	// ErrInsufficientAllowance indicates that the renter's allowance is less
    21  	// than the amount necessary to store at least one sector
    22  	ErrInsufficientAllowance = errors.New("allowance is not large enough to cover fees of contract creation")
    23  	errTooExpensive          = errors.New("host price was too high")
    24  )
    25  
    26  type (
    27  	// fileContractRenewal is an instruction to renew a file contract.
    28  	fileContractRenewal struct {
    29  		id     types.FileContractID
    30  		amount types.Currency
    31  	}
    32  )
    33  
    34  // contractEndHeight returns the height at which the Contractor's contracts
    35  // end. If there are no contracts, it returns zero.
    36  func (c *Contractor) contractEndHeight() types.BlockHeight {
    37  	return c.currentPeriod + c.allowance.Period
    38  }
    39  
    40  // managedContractUtility returns the ContractUtility for a contract with a given id.
    41  func (c *Contractor) managedContractUtility(id types.FileContractID) (modules.ContractUtility, bool) {
    42  	rc, exists := c.staticContracts.View(id)
    43  	if !exists {
    44  		return modules.ContractUtility{}, false
    45  	}
    46  	return rc.Utility, true
    47  }
    48  
    49  // managedEstimateRenewFundingRequirements estimates the amount of money that a
    50  // contract is going to need in the next billing cycle by looking at how much
    51  // storage is in the contract and what the historic usage pattern of the
    52  // contract has been.
    53  //
    54  // TODO: This looks at the spending metrics of the contract being renewed, but
    55  // if the contract itself is a refresh of a contract that ran out of funds,
    56  // these estimates may be off because they do not include the spending from the
    57  // same-period parent contracts of this contract. This estimator function needs
    58  // to be extended to support adding up all the parent spending too. These
    59  // spending estimates will apply to uploading and downloading, but not to
    60  // storage or fees or contract price.
    61  func (c *Contractor) managedEstimateRenewFundingRequirements(contract modules.RenterContract, blockHeight types.BlockHeight, allowance modules.Allowance) (types.Currency, error) {
    62  	// Fetch the host pricing to use in the estimate.
    63  	host, exists := c.hdb.Host(contract.HostPublicKey)
    64  	if !exists {
    65  		return types.ZeroCurrency, errors.New("could not find host in hostdb")
    66  	}
    67  
    68  	// Estimate the amount of money that's going to be needed for existing
    69  	// storage.
    70  	dataStored := contract.Transaction.FileContractRevisions[0].NewFileSize
    71  	maintenanceCost := types.NewCurrency64(dataStored).Mul64(uint64(allowance.Period)).Mul(host.StoragePrice)
    72  
    73  	// Estimate the amount of money that's going to be needed for new storage
    74  	// based on the amount of new storage added in the previous period. Account
    75  	// for both the storage price as well as the upload price.
    76  	//
    77  	// TODO: We are currently using a very crude method to estimate the amount
    78  	// of data uploaded, the host could have easily changed prices partway
    79  	// through the contract, which would cause this estimate to fail.
    80  	prevUploadSpending := contract.UploadSpending
    81  	prevUploadDataEstimate := contract.UploadSpending.Div(host.UploadBandwidthPrice)
    82  	// Sanity check - the host may have changed prices, make sure we aren't
    83  	// assuming an unreasonable amount of data.
    84  	if types.NewCurrency64(dataStored).Cmp(prevUploadDataEstimate) < 0 {
    85  		prevUploadDataEstimate = types.NewCurrency64(dataStored)
    86  	}
    87  	// The estimated cost for new upload spending is the previous upload
    88  	// bandwidth plus the implied storage cost for all of the new data.
    89  	newUploadsCost := prevUploadSpending.Add(prevUploadDataEstimate.Mul64(uint64(allowance.Period)).Mul(host.StoragePrice))
    90  
    91  	// Estimate the amount of money that's going to be spent on downloads.
    92  	newDownloadsCost := contract.DownloadSpending
    93  
    94  	// We will also need to pay the host contract price.
    95  	contractPrice := host.ContractPrice
    96  
    97  	// Aggregate all estimates so far to compute the estimated siafunds fees.
    98  	// The transaction fees are not included in the siafunds estimate because
    99  	// users are not charged siafund fees on money that doesn't go into the file
   100  	// contract (and the transaction fee goes to the miners, not the file
   101  	// contract).
   102  	beforeSiafundFeesEstimate := maintenanceCost.Add(newUploadsCost).Add(newDownloadsCost).Add(contractPrice)
   103  	afterSiafundFeesEstimate := types.Tax(blockHeight, beforeSiafundFeesEstimate).Add(beforeSiafundFeesEstimate)
   104  
   105  	// Get an estimate for how much money we will be charged before going into
   106  	// the transaction pool.
   107  	_, maxTxnFee := c.tpool.FeeEstimation()
   108  	txnFees := maxTxnFee.Mul64(modules.EstimatedFileContractTransactionSetSize)
   109  
   110  	// Add them all up and then return the estimate plus 33% for error margin
   111  	// and just general volatility of usage pattern.
   112  	estimatedCost := afterSiafundFeesEstimate.Add(txnFees)
   113  	estimatedCost = estimatedCost.Add(estimatedCost.Div64(3))
   114  
   115  	// Check for a sane minimum. The contractor should not be forming contracts
   116  	// with less than 'fileContractMinimumFunding / (num contracts)' of the
   117  	// value of the allowance.
   118  	minimum := allowance.Funds.MulFloat(fileContractMinimumFunding).Div64(allowance.Hosts)
   119  	if estimatedCost.Cmp(minimum) < 0 {
   120  		estimatedCost = minimum
   121  	}
   122  	return estimatedCost, nil
   123  }
   124  
   125  // managedInterruptContractMaintenance will issue an interrupt signal to any
   126  // running maintenance, stopping that maintenance. If there are multiple threads
   127  // running maintenance, they will all be stopped.
   128  func (c *Contractor) managedInterruptContractMaintenance() {
   129  	// Spin up a thread to grab the maintenance lock. Signal that the lock was
   130  	// acquired after the lock is acquired.
   131  	gotLock := make(chan struct{})
   132  	go func() {
   133  		c.maintenanceLock.Lock()
   134  		close(gotLock)
   135  		c.maintenanceLock.Unlock()
   136  	}()
   137  
   138  	// There may be multiple threads contending for the maintenance lock. Issue
   139  	// interrupts repeatedly until we get a signal that the maintenance lock has
   140  	// been acquired.
   141  	for {
   142  		select {
   143  		case <-gotLock:
   144  			return
   145  		case c.interruptMaintenance <- struct{}{}:
   146  		}
   147  	}
   148  }
   149  
   150  // managedMarkContractsUtility checks every active contract in the contractor and
   151  // figures out whether the contract is useful for uploading, and whether the
   152  // contract should be renewed.
   153  func (c *Contractor) managedMarkContractsUtility() error {
   154  	// Pull a new set of hosts from the hostdb that could be used as a new set
   155  	// to match the allowance. The lowest scoring host of these new hosts will
   156  	// be used as a baseline for determining whether our existing contracts are
   157  	// worthwhile.
   158  	c.mu.RLock()
   159  	hostCount := int(c.allowance.Hosts)
   160  	c.mu.RUnlock()
   161  	hosts, err := c.hdb.RandomHosts(hostCount+randomHostsBufferForScore, nil)
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	// Find the minimum score that a host is allowed to have to be considered
   167  	// good for upload.
   168  	var minScore types.Currency
   169  	if len(hosts) > 0 {
   170  		lowestScore := c.hdb.ScoreBreakdown(hosts[0]).Score
   171  		for i := 1; i < len(hosts); i++ {
   172  			score := c.hdb.ScoreBreakdown(hosts[i]).Score
   173  			if score.Cmp(lowestScore) < 0 {
   174  				lowestScore = score
   175  			}
   176  		}
   177  		// Set the minimum acceptable score to a factor of the lowest score.
   178  		minScore = lowestScore.Div(scoreLeeway)
   179  	}
   180  
   181  	// Update utility fields for each contract.
   182  	for _, contract := range c.staticContracts.ViewAll() {
   183  		utility := func() (u modules.ContractUtility) {
   184  			// Start the contract in good standing if the utility wasn't
   185  			// locked.
   186  			if !u.Locked {
   187  				u.GoodForUpload = true
   188  				u.GoodForRenew = true
   189  			}
   190  
   191  			host, exists := c.hdb.Host(contract.HostPublicKey)
   192  			// Contract has no utility if the host is not in the database.
   193  			if !exists {
   194  				u.GoodForUpload = false
   195  				u.GoodForRenew = false
   196  				return
   197  			}
   198  			// Contract has no utility if the score is poor.
   199  			if !minScore.IsZero() && c.hdb.ScoreBreakdown(host).Score.Cmp(minScore) < 0 {
   200  				u.GoodForUpload = false
   201  				u.GoodForRenew = false
   202  				return
   203  			}
   204  			// Contract has no utility if the host is offline.
   205  			if isOffline(host) {
   206  				u.GoodForUpload = false
   207  				u.GoodForRenew = false
   208  				return
   209  			}
   210  			// Contract should not be used for uploading if the time has come to
   211  			// renew the contract.
   212  			c.mu.RLock()
   213  			blockHeight := c.blockHeight
   214  			renewWindow := c.allowance.RenewWindow
   215  			c.mu.RUnlock()
   216  			if blockHeight+renewWindow >= contract.EndHeight {
   217  				u.GoodForUpload = false
   218  				return
   219  			}
   220  			return
   221  		}()
   222  
   223  		// Apply changes.
   224  		err := c.managedUpdateContractUtility(contract.ID, utility)
   225  		if err != nil {
   226  			return err
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  // managedNewContract negotiates an initial file contract with the specified
   233  // host, saves it, and returns it.
   234  func (c *Contractor) managedNewContract(host modules.HostDBEntry, contractFunding types.Currency, endHeight types.BlockHeight) (modules.RenterContract, error) {
   235  	// reject hosts that are too expensive
   236  	if host.StoragePrice.Cmp(maxStoragePrice) > 0 {
   237  		return modules.RenterContract{}, errTooExpensive
   238  	}
   239  	// cap host.MaxCollateral
   240  	if host.MaxCollateral.Cmp(maxCollateral) > 0 {
   241  		host.MaxCollateral = maxCollateral
   242  	}
   243  
   244  	// get an address to use for negotiation
   245  	uc, err := c.wallet.NextAddress()
   246  	if err != nil {
   247  		return modules.RenterContract{}, err
   248  	}
   249  
   250  	// create contract params
   251  	c.mu.RLock()
   252  	params := proto.ContractParams{
   253  		Host:          host,
   254  		Funding:       contractFunding,
   255  		StartHeight:   c.blockHeight,
   256  		EndHeight:     endHeight,
   257  		RefundAddress: uc.UnlockHash(),
   258  	}
   259  	c.mu.RUnlock()
   260  
   261  	// create transaction builder
   262  	txnBuilder, err := c.wallet.StartTransaction()
   263  	if err != nil {
   264  		return modules.RenterContract{}, err
   265  	}
   266  
   267  	contract, err := c.staticContracts.FormContract(params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan())
   268  	if err != nil {
   269  		txnBuilder.Drop()
   270  		return modules.RenterContract{}, err
   271  	}
   272  
   273  	// Add a mapping from the contract's id to the public key of the host.
   274  	c.mu.Lock()
   275  	c.contractIDToPubKey[contract.ID] = contract.HostPublicKey
   276  	_, exists := c.pubKeysToContractID[string(contract.HostPublicKey.Key)]
   277  	if exists {
   278  		c.mu.Unlock()
   279  		txnBuilder.Drop()
   280  		return modules.RenterContract{}, fmt.Errorf("We already have a contract with host %v", contract.HostPublicKey)
   281  	}
   282  	c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID
   283  	c.mu.Unlock()
   284  
   285  	contractValue := contract.RenterFunds
   286  	c.log.Printf("Formed contract %v with %v for %v", contract.ID, host.NetAddress, contractValue.HumanString())
   287  	return contract, nil
   288  }
   289  
   290  // managedPrunePubkeyMap will delete any pubkeys in the pubKeysToContractID map
   291  // that no longer map to an active contract.
   292  func (c *Contractor) managedPrunePubkeyMap() {
   293  	allContracts := c.staticContracts.ViewAll()
   294  	pks := make(map[string]struct{})
   295  	for _, c := range allContracts {
   296  		pks[string(c.HostPublicKey.Key)] = struct{}{}
   297  	}
   298  	c.mu.Lock()
   299  	for pk := range c.pubKeysToContractID {
   300  		if _, exists := pks[pk]; !exists {
   301  			delete(c.pubKeysToContractID, pk)
   302  		}
   303  	}
   304  	c.mu.Unlock()
   305  }
   306  
   307  // managedRenew negotiates a new contract for data already stored with a host.
   308  // It returns the new contract. This is a blocking call that performs network
   309  // I/O.
   310  func (c *Contractor) managedRenew(sc *proto.SafeContract, contractFunding types.Currency, newEndHeight types.BlockHeight) (modules.RenterContract, error) {
   311  	// For convenience
   312  	contract := sc.Metadata()
   313  	// Sanity check - should not be renewing a bad contract.
   314  	utility, ok := c.managedContractUtility(contract.ID)
   315  	if !ok || !utility.GoodForRenew {
   316  		c.log.Critical(fmt.Sprintf("Renewing a contract that has been marked as !GoodForRenew %v/%v",
   317  			ok, utility.GoodForRenew))
   318  	}
   319  
   320  	// Fetch the host associated with this contract.
   321  	host, ok := c.hdb.Host(contract.HostPublicKey)
   322  	if !ok {
   323  		return modules.RenterContract{}, errors.New("no record of that host")
   324  	} else if host.StoragePrice.Cmp(maxStoragePrice) > 0 {
   325  		return modules.RenterContract{}, errTooExpensive
   326  	}
   327  	// cap host.MaxCollateral
   328  	if host.MaxCollateral.Cmp(maxCollateral) > 0 {
   329  		host.MaxCollateral = maxCollateral
   330  	}
   331  
   332  	// get an address to use for negotiation
   333  	uc, err := c.wallet.NextAddress()
   334  	if err != nil {
   335  		return modules.RenterContract{}, err
   336  	}
   337  
   338  	// create contract params
   339  	c.mu.RLock()
   340  	params := proto.ContractParams{
   341  		Host:          host,
   342  		Funding:       contractFunding,
   343  		StartHeight:   c.blockHeight,
   344  		EndHeight:     newEndHeight,
   345  		RefundAddress: uc.UnlockHash(),
   346  	}
   347  	c.mu.RUnlock()
   348  
   349  	// execute negotiation protocol
   350  	txnBuilder, err := c.wallet.StartTransaction()
   351  	if err != nil {
   352  		return modules.RenterContract{}, err
   353  	}
   354  	newContract, err := c.staticContracts.Renew(sc, params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan())
   355  	if err != nil {
   356  		txnBuilder.Drop() // return unused outputs to wallet
   357  		return modules.RenterContract{}, err
   358  	}
   359  
   360  	// Add a mapping from the contract's id to the public key of the host. This
   361  	// will destroy the previous mapping from pubKey to contract id but other
   362  	// modules are only interested in the most recent contract anyway.
   363  	c.mu.Lock()
   364  	c.contractIDToPubKey[newContract.ID] = newContract.HostPublicKey
   365  	c.pubKeysToContractID[string(newContract.HostPublicKey.Key)] = newContract.ID
   366  	c.mu.Unlock()
   367  
   368  	return newContract, nil
   369  }
   370  
   371  // managedRenewContract will use the renew instructions to renew a contract,
   372  // returning the amount of money that was put into the contract for renewal.
   373  func (c *Contractor) managedRenewContract(renewInstructions fileContractRenewal, currentPeriod types.BlockHeight, allowance modules.Allowance, blockHeight types.BlockHeight) (fundsSpent types.Currency, err error) {
   374  	// Pull the variables out of the renewal.
   375  	id := renewInstructions.id
   376  	amount := renewInstructions.amount
   377  
   378  	// Mark the contract as being renewed, and defer logic to unmark it
   379  	// once renewing is complete.
   380  	c.mu.Lock()
   381  	c.renewing[id] = true
   382  	c.mu.Unlock()
   383  	defer func() {
   384  		c.mu.Lock()
   385  		delete(c.renewing, id)
   386  		c.mu.Unlock()
   387  	}()
   388  
   389  	// Wait for any active editors and downloaders to finish for this
   390  	// contract, and then grab the latest revision.
   391  	c.mu.RLock()
   392  	e, eok := c.editors[id]
   393  	d, dok := c.downloaders[id]
   394  	c.mu.RUnlock()
   395  	if eok {
   396  		e.invalidate()
   397  	}
   398  	if dok {
   399  		d.invalidate()
   400  	}
   401  
   402  	// Fetch the contract that we are renewing.
   403  	oldContract, exists := c.staticContracts.Acquire(id)
   404  	if !exists {
   405  		return types.ZeroCurrency, errors.New("contract no longer exists")
   406  	}
   407  	// Return the contract if it's not useful for renewing.
   408  	oldUtility, ok := c.managedContractUtility(id)
   409  	if !ok || !oldUtility.GoodForRenew {
   410  		c.log.Printf("Contract %v slated for renew is marked not good for renew %v/%v",
   411  			id, ok, oldUtility.GoodForRenew)
   412  		c.staticContracts.Return(oldContract)
   413  		return types.ZeroCurrency, errors.New("contract is marked not good for renew")
   414  	}
   415  
   416  	// Calculate endHeight for renewed contracts
   417  	endHeight := currentPeriod + allowance.Period
   418  
   419  	// Perform the actual renew. If the renew fails, return the
   420  	// contract. If the renew fails we check how often it has failed
   421  	// before. Once it has failed for a certain number of blocks in a
   422  	// row and reached its second half of the renew window, we give up
   423  	// on renewing it and set goodForRenew to false.
   424  	newContract, errRenew := c.managedRenew(oldContract, amount, endHeight)
   425  	if errRenew != nil {
   426  		// Increment the number of failed renews for the contract if it
   427  		// was the host's fault.
   428  		if modules.IsHostsFault(errRenew) {
   429  			c.mu.Lock()
   430  			c.numFailedRenews[oldContract.Metadata().ID]++
   431  			c.mu.Unlock()
   432  		}
   433  
   434  		// Check if contract has to be replaced.
   435  		md := oldContract.Metadata()
   436  		c.mu.RLock()
   437  		numRenews, failedBefore := c.numFailedRenews[md.ID]
   438  		c.mu.RUnlock()
   439  		secondHalfOfWindow := blockHeight+allowance.RenewWindow/2 >= md.EndHeight
   440  		replace := numRenews >= consecutiveRenewalsBeforeReplacement
   441  		if failedBefore && secondHalfOfWindow && replace {
   442  			oldUtility.GoodForRenew = false
   443  			oldUtility.GoodForUpload = false
   444  			oldUtility.Locked = true
   445  			err := oldContract.UpdateUtility(oldUtility)
   446  			if err != nil {
   447  				c.log.Println("WARN: failed to mark contract as !goodForRenew:", err)
   448  			}
   449  			c.log.Printf("WARN: failed to renew %v, marked as bad: %v\n",
   450  				oldContract.Metadata().HostPublicKey, errRenew)
   451  			c.staticContracts.Return(oldContract)
   452  			return types.ZeroCurrency, errors.AddContext(errRenew, "contract marked as bad for too many consecutive failed renew attempts")
   453  		}
   454  
   455  		// Seems like it doesn't have to be replaced yet. Log the
   456  		// failure and number of renews that have failed so far.
   457  		c.log.Printf("WARN: failed to renew contract %v [%v]: %v\n",
   458  			oldContract.Metadata().HostPublicKey, numRenews, errRenew)
   459  		c.staticContracts.Return(oldContract)
   460  		return types.ZeroCurrency, errors.AddContext(errRenew, "contract renewal with host was unsuccessful")
   461  	}
   462  	c.log.Printf("Renewed contract %v\n", id)
   463  
   464  	// Update the utility values for the new contract, and for the old
   465  	// contract.
   466  	newUtility := modules.ContractUtility{
   467  		GoodForUpload: true,
   468  		GoodForRenew:  true,
   469  	}
   470  	if err := c.managedUpdateContractUtility(newContract.ID, newUtility); err != nil {
   471  		c.log.Println("Failed to update the contract utilities", err)
   472  		return amount, nil // Error is not returned because the renew succeeded.
   473  	}
   474  	oldUtility.GoodForRenew = false
   475  	oldUtility.GoodForUpload = false
   476  	if err := oldContract.UpdateUtility(oldUtility); err != nil {
   477  		c.log.Println("Failed to update the contract utilities", err)
   478  		return amount, nil // Error is not returned because the renew succeeded.
   479  	}
   480  
   481  	// Lock the contractor as we update it to use the new contract
   482  	// instead of the old contract.
   483  	c.mu.Lock()
   484  	defer c.mu.Unlock()
   485  	// Delete the old contract.
   486  	c.staticContracts.Delete(oldContract)
   487  	// Store the contract in the record of historic contracts.
   488  	c.oldContracts[id] = oldContract.Metadata()
   489  	// Save the contractor.
   490  	err = c.saveSync()
   491  	if err != nil {
   492  		c.log.Println("Failed to save the contractor after creating a new contract.")
   493  	}
   494  	return amount, nil
   495  }
   496  
   497  // threadedContractMaintenance checks the set of contracts that the contractor
   498  // has against the allownace, renewing any contracts that need to be renewed,
   499  // dropping contracts which are no longer worthwhile, and adding contracts if
   500  // there are not enough.
   501  //
   502  // Between each network call, the thread checks whether a maintenance interrupt
   503  // signal is being sent. If so, maintenance returns, yielding to whatever thread
   504  // issued the interrupt.
   505  func (c *Contractor) threadedContractMaintenance() {
   506  	// Threading protection.
   507  	err := c.tg.Add()
   508  	if err != nil {
   509  		return
   510  	}
   511  	defer c.tg.Done()
   512  
   513  	// Archive contracts that need to be archived before doing additional
   514  	// maintenance, and then prune the pubkey map.
   515  	c.managedArchiveContracts()
   516  	c.managedPrunePubkeyMap()
   517  
   518  	// Nothing to do if there are no hosts.
   519  	c.mu.RLock()
   520  	wantedHosts := c.allowance.Hosts
   521  	c.mu.RUnlock()
   522  	if wantedHosts <= 0 {
   523  		return
   524  	}
   525  
   526  	// Only one instance of this thread should be running at a time. Under
   527  	// normal conditions, fine to return early if another thread is already
   528  	// doing maintenance. The next block will trigger another round. Under
   529  	// testing, control is insufficient if the maintenance loop isn't guaranteed
   530  	// to run.
   531  	if build.Release == "testing" {
   532  		c.maintenanceLock.Lock()
   533  	} else if !c.maintenanceLock.TryLock() {
   534  		return
   535  	}
   536  	defer c.maintenanceLock.Unlock()
   537  
   538  	// Update the utility fields for this contract based on the most recent
   539  	// hostdb.
   540  	if err := c.managedMarkContractsUtility(); err != nil {
   541  		c.log.Println("WARNING: wasn't able to mark contracts", err)
   542  		return
   543  	}
   544  
   545  	// Create the renewSet and refreshSet. Each is a list of contracts that need
   546  	// to be renewed, paired with the amount of money to use in each renewal.
   547  	//
   548  	// The renewSet is specifically contracts which are being renewed because
   549  	// they are about to expire. And the refreshSet is contracts that are being
   550  	// renewed because they are out of money.
   551  	//
   552  	// The contractor will prioritize contracts in the renewSet over contracts
   553  	// in the refreshSet. If the wallet does not have enough money, or if the
   554  	// allowance does not have enough money, the contractor will prefer to save
   555  	// data in the long term rather than renew a contract.
   556  	var renewSet []fileContractRenewal
   557  	var refreshSet []fileContractRenewal
   558  
   559  	// The rest of this function needs to know a few of the stateful variables
   560  	// from the contractor, build those up under a lock so that the rest of the
   561  	// function can execute without lock contention.
   562  	c.mu.RLock()
   563  	currentPeriod := c.currentPeriod
   564  	allowance := c.allowance
   565  	blockHeight := c.blockHeight
   566  	c.mu.RUnlock()
   567  	endHeight := currentPeriod + allowance.Period
   568  
   569  	// Iterate through the contracts again, figuring out which contracts to
   570  	// renew and how much extra funds to renew them with.
   571  	for _, contract := range c.staticContracts.ViewAll() {
   572  		// Skip any contracts which do not exist or are otherwise unworthy for
   573  		// renewal.
   574  		utility, ok := c.managedContractUtility(contract.ID)
   575  		if !ok || !utility.GoodForRenew {
   576  			continue
   577  		}
   578  
   579  		// If the contract needs to be renewed because it is about to expire,
   580  		// calculate a spending for the contract that is proportional to how
   581  		// much money was spend on the contract throughout this billing cycle
   582  		// (which is now ending).
   583  		if blockHeight+allowance.RenewWindow >= contract.EndHeight {
   584  			renewAmount, err := c.managedEstimateRenewFundingRequirements(contract, blockHeight, allowance)
   585  			if err != nil {
   586  				continue
   587  			}
   588  			renewSet = append(renewSet, fileContractRenewal{
   589  				id:     contract.ID,
   590  				amount: renewAmount,
   591  			})
   592  			continue
   593  		}
   594  
   595  		// Check if the contract is empty. We define a contract as being empty
   596  		// if less than 'minContractFundRenewalThreshold' funds are remaining
   597  		// (3% at time of writing), or if there is less than 3 sectors worth of
   598  		// storage+upload+download remaining.
   599  		host, _ := c.hdb.Host(contract.HostPublicKey)
   600  		blockBytes := types.NewCurrency64(modules.SectorSize * uint64(allowance.Period))
   601  		sectorStoragePrice := host.StoragePrice.Mul(blockBytes)
   602  		sectorUploadBandwidthPrice := host.UploadBandwidthPrice.Mul64(modules.SectorSize)
   603  		sectorDownloadBandwidthPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize)
   604  		sectorBandwidthPrice := sectorUploadBandwidthPrice.Add(sectorDownloadBandwidthPrice)
   605  		sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice)
   606  		percentRemaining, _ := big.NewRat(0, 1).SetFrac(contract.RenterFunds.Big(), contract.TotalCost.Big()).Float64()
   607  		if contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0 || percentRemaining < minContractFundRenewalThreshold {
   608  			// Renew the contract with double the amount of funds that the
   609  			// contract had previously. The reason that we double the funding
   610  			// instead of doing anything more clever is that we don't know what
   611  			// the usage pattern has been. The spending could have all occured
   612  			// in one burst recently, and the user might need a contract that
   613  			// has substantially more money in it.
   614  			//
   615  			// We double so that heavily used contracts can grow in funding
   616  			// quickly without consuming too many transaction fees, however this
   617  			// does mean that a larger percentage of funds get locked away from
   618  			// the user in the event that the user stops uploading immediately
   619  			// after the renew.
   620  			refreshSet = append(refreshSet, fileContractRenewal{
   621  				id:     contract.ID,
   622  				amount: contract.TotalCost.Mul64(2),
   623  			})
   624  		}
   625  	}
   626  	if len(renewSet) != 0 {
   627  		c.log.Printf("renewing %v contracts", len(renewSet))
   628  	}
   629  
   630  	// Remove contracts that are not scheduled for renew from the
   631  	// firstFailedRenew map. We do this by making a new map entirely and copying
   632  	// over all the elements that still matter.
   633  	c.mu.Lock()
   634  	newFirstFailedRenew := make(map[types.FileContractID]types.BlockHeight)
   635  	for _, r := range renewSet {
   636  		if _, exists := c.numFailedRenews[r.id]; exists {
   637  			newFirstFailedRenew[r.id] = c.numFailedRenews[r.id]
   638  		}
   639  	}
   640  	c.numFailedRenews = newFirstFailedRenew
   641  	c.mu.Unlock()
   642  
   643  	// Depend on the PeriodSpending function to get a breakdown of spending in
   644  	// the contractor. Then use that to determine how many funds remain
   645  	// available in the allowance for renewals.
   646  	spending := c.PeriodSpending()
   647  	var fundsRemaining types.Currency
   648  	// Check for an underflow. This can happen if the user reduced their
   649  	// allowance at some point to less than what we've already spent.
   650  	if spending.TotalAllocated.Cmp(allowance.Funds) < 0 {
   651  		fundsRemaining = allowance.Funds.Sub(spending.TotalAllocated)
   652  	}
   653  
   654  	// Go through the contracts we've assembled for renewal. Any contracts that
   655  	// need to be renewed because they are expiring (renewSet) get priority over
   656  	// contracts that need to be renewed because they have exhausted their funds
   657  	// (refreshSet). If there is not enough money available, the more expensive
   658  	// contracts will be skipped.
   659  	//
   660  	// TODO: We need some sort of global warning system so that we can alert the
   661  	// user to the fact that they do not have enough money to keep their
   662  	// contracts going in the event that we run out of funds.
   663  	for _, renewal := range renewSet {
   664  		// Skip this renewal if we don't have enough funds remaining.
   665  		if renewal.amount.Cmp(fundsRemaining) > 0 {
   666  			continue
   667  		}
   668  
   669  		// Renew one contract. The error is ignored because the renew function
   670  		// already will have logged the error, and in the event of an error,
   671  		// 'fundsSpent' will return '0'.
   672  		fundsSpent, _ := c.managedRenewContract(renewal, currentPeriod, allowance, blockHeight)
   673  		fundsRemaining = fundsRemaining.Sub(fundsSpent)
   674  
   675  		// Return here if an interrupt or kill signal has been sent.
   676  		select {
   677  		case <-c.tg.StopChan():
   678  			return
   679  		case <-c.interruptMaintenance:
   680  			return
   681  		default:
   682  		}
   683  	}
   684  	for _, renewal := range refreshSet {
   685  		// Skip this renewal if we don't have enough funds remaining.
   686  		if renewal.amount.Cmp(fundsRemaining) > 0 {
   687  			continue
   688  		}
   689  
   690  		// Renew one contract. The error is ignored because the renew function
   691  		// already will have logged the error, and in the event of an error,
   692  		// 'fundsSpent' will return '0'.
   693  		fundsSpent, _ := c.managedRenewContract(renewal, currentPeriod, allowance, blockHeight)
   694  		fundsRemaining = fundsRemaining.Sub(fundsSpent)
   695  
   696  		// Return here if an interrupt or kill signal has been sent.
   697  		select {
   698  		case <-c.tg.StopChan():
   699  			return
   700  		case <-c.interruptMaintenance:
   701  			return
   702  		default:
   703  		}
   704  	}
   705  
   706  	// Count the number of contracts which are good for uploading, and then make
   707  	// more as needed to fill the gap.
   708  	uploadContracts := 0
   709  	for _, id := range c.staticContracts.IDs() {
   710  		if cu, ok := c.managedContractUtility(id); ok && cu.GoodForUpload {
   711  			uploadContracts++
   712  		}
   713  	}
   714  	c.mu.RLock()
   715  	neededContracts := int(c.allowance.Hosts) - uploadContracts
   716  	c.mu.RUnlock()
   717  	if neededContracts <= 0 {
   718  		return
   719  	}
   720  
   721  	// Assemble an exclusion list that includes all of the hosts that we already
   722  	// have contracts with, then select a new batch of hosts to attempt contract
   723  	// formation with.
   724  	c.mu.RLock()
   725  	var exclude []types.SiaPublicKey
   726  	for _, contract := range c.staticContracts.ViewAll() {
   727  		exclude = append(exclude, contract.HostPublicKey)
   728  	}
   729  	initialContractFunds := c.allowance.Funds.Div64(c.allowance.Hosts).Div64(3)
   730  	c.mu.RUnlock()
   731  	hosts, err := c.hdb.RandomHosts(neededContracts*2+randomHostsBufferForScore, exclude)
   732  	if err != nil {
   733  		c.log.Println("WARN: not forming new contracts:", err)
   734  		return
   735  	}
   736  
   737  	// Form contracts with the hosts one at a time, until we have enough
   738  	// contracts.
   739  	for _, host := range hosts {
   740  		// Determine if we have enough money to form a new contract.
   741  		if fundsRemaining.Cmp(initialContractFunds) < 0 {
   742  			c.log.Println("WARN: need to form new contracts, but unable to because of a low allowance")
   743  			break
   744  		}
   745  
   746  		// Attempt forming a contract with this host.
   747  		newContract, err := c.managedNewContract(host, initialContractFunds, endHeight)
   748  		if err != nil {
   749  			c.log.Printf("Attempted to form a contract with %v, but negotiation failed: %v\n", host.NetAddress, err)
   750  			continue
   751  		}
   752  
   753  		// Add this contract to the contractor and save.
   754  		err = c.managedUpdateContractUtility(newContract.ID, modules.ContractUtility{
   755  			GoodForUpload: true,
   756  			GoodForRenew:  true,
   757  		})
   758  		if err != nil {
   759  			c.log.Println("Failed to update the contract utilities", err)
   760  			return
   761  		}
   762  		c.mu.Lock()
   763  		err = c.saveSync()
   764  		c.mu.Unlock()
   765  		if err != nil {
   766  			c.log.Println("Unable to save the contractor:", err)
   767  		}
   768  
   769  		// Quit the loop if we've replaced all needed contracts.
   770  		neededContracts--
   771  		if neededContracts <= 0 {
   772  			break
   773  		}
   774  
   775  		// Soft sleep before making the next contract.
   776  		select {
   777  		case <-c.tg.StopChan():
   778  			return
   779  		case <-c.interruptMaintenance:
   780  			return
   781  		default:
   782  		}
   783  	}
   784  }
   785  
   786  // managedUpdateContractUtility is a helper function that acquires a contract, updates
   787  // its ContractUtility and returns the contract again.
   788  func (c *Contractor) managedUpdateContractUtility(id types.FileContractID, utility modules.ContractUtility) error {
   789  	safeContract, ok := c.staticContracts.Acquire(id)
   790  	if !ok {
   791  		return errors.New("failed to acquire contract for update")
   792  	}
   793  	defer c.staticContracts.Return(safeContract)
   794  	return safeContract.UpdateUtility(utility)
   795  }