github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/contractor/contractmaintenance.go (about)

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