github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/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  	"errors"
     9  	"math/big"
    10  	"time"
    11  
    12  	"github.com/Synthesix/Sia/build"
    13  	"github.com/Synthesix/Sia/modules"
    14  	"github.com/Synthesix/Sia/modules/renter/proto"
    15  	"github.com/Synthesix/Sia/types"
    16  )
    17  
    18  var (
    19  	// ErrInsufficientAllowance indicates that the renter's allowance is less
    20  	// than the amount necessary to store at least one sector
    21  	ErrInsufficientAllowance = errors.New("allowance is not large enough to cover fees of contract creation")
    22  	errTooExpensive          = errors.New("host price was too high")
    23  )
    24  
    25  // contractEndHeight returns the height at which the Contractor's contracts
    26  // end. If there are no contracts, it returns zero.
    27  func (c *Contractor) contractEndHeight() types.BlockHeight {
    28  	return c.currentPeriod + c.allowance.Period
    29  }
    30  
    31  // managedInterruptContractMaintenance will issue an interrupt signal to any
    32  // running maintenance, stopping that maintenance. If there are multiple threads
    33  // running maintenance, they will all be stopped.
    34  func (c *Contractor) managedInterruptContractMaintenance() {
    35  	// Spin up a thread to grab the maintenance lock. Signal that the lock was
    36  	// acquired after the lock is acquired.
    37  	gotLock := make(chan struct{})
    38  	go func() {
    39  		c.maintenanceLock.Lock()
    40  		close(gotLock)
    41  		c.maintenanceLock.Unlock()
    42  	}()
    43  
    44  	// There may be multiple threads contending for the maintenance lock. Issue
    45  	// interrupts repeatedly until we get a signal that the maintenance lock has
    46  	// been acquired.
    47  	for {
    48  		select {
    49  		case <-gotLock:
    50  			return
    51  		case c.interruptMaintenance <- struct{}{}:
    52  		}
    53  	}
    54  }
    55  
    56  // managedMarkContractsUtility checks every active contract in the contractor and
    57  // figures out whether the contract is useful for uploading, and whehter the
    58  // contract should be renewed.
    59  func (c *Contractor) managedMarkContractsUtility() {
    60  	// Pull a new set of hosts from the hostdb that could be used as a new set
    61  	// to match the allowance. The lowest scoring host of these new hosts will
    62  	// be used as a baseline for determining whether our existing contracts are
    63  	// worthwhile.
    64  	c.mu.RLock()
    65  	hostCount := int(c.allowance.Hosts)
    66  	c.mu.RUnlock()
    67  	hosts := c.hdb.RandomHosts(hostCount+minScoreHostBuffer, nil)
    68  
    69  	// Find the minimum score that a host is allowed to have to be considered
    70  	// good for upload.
    71  	var minScore types.Currency
    72  	if len(hosts) > 0 {
    73  		lowestScore := c.hdb.ScoreBreakdown(hosts[0]).Score
    74  		for i := 1; i < len(hosts); i++ {
    75  			score := c.hdb.ScoreBreakdown(hosts[i]).Score
    76  			if score.Cmp(lowestScore) < 0 {
    77  				lowestScore = score
    78  			}
    79  		}
    80  		// Set the minimum acceptable score to a factor of the lowest score.
    81  		minScore = lowestScore.Div(scoreLeeway)
    82  	}
    83  
    84  	// Update utility fields for each contract.
    85  	for _, contract := range c.contracts.ViewAll() {
    86  		utility := func() (u modules.ContractUtility) {
    87  			// Start the contract in good standing.
    88  			u.GoodForUpload = true
    89  			u.GoodForRenew = true
    90  
    91  			host, exists := c.hdb.Host(contract.HostPublicKey)
    92  			// Contract has no utility if the host is not in the database.
    93  			if !exists {
    94  				u.GoodForUpload = false
    95  				u.GoodForRenew = false
    96  				return
    97  			}
    98  			// Contract has no utility if the score is poor.
    99  			if !minScore.IsZero() && c.hdb.ScoreBreakdown(host).Score.Cmp(minScore) < 0 {
   100  				u.GoodForUpload = false
   101  				u.GoodForRenew = false
   102  				return
   103  			}
   104  			// Contract has no utility if the host is offline.
   105  			if isOffline(host) {
   106  				u.GoodForUpload = false
   107  				u.GoodForRenew = false
   108  				return
   109  			}
   110  			// Contract has no utility if renew has already completed. (grab some
   111  			// extra values while we have the mutex)
   112  			c.mu.RLock()
   113  			blockHeight := c.blockHeight
   114  			renewWindow := c.allowance.RenewWindow
   115  			_, renewedPreviously := c.renewedIDs[contract.ID]
   116  			c.mu.RUnlock()
   117  			if renewedPreviously {
   118  				u.GoodForUpload = false
   119  				u.GoodForRenew = false
   120  				return
   121  			}
   122  
   123  			// Contract should not be used for uploading if the time has come to
   124  			// renew the contract.
   125  			if blockHeight+renewWindow >= contract.EndHeight {
   126  				u.GoodForUpload = false
   127  				return
   128  			}
   129  			return
   130  		}()
   131  
   132  		// Apply changes.
   133  		c.mu.Lock()
   134  		c.contractUtilities[contract.ID] = utility
   135  		c.mu.Unlock()
   136  	}
   137  }
   138  
   139  // managedNewContract negotiates an initial file contract with the specified
   140  // host, saves it, and returns it.
   141  func (c *Contractor) managedNewContract(host modules.HostDBEntry, contractFunding types.Currency, endHeight types.BlockHeight) (modules.RenterContract, error) {
   142  	// reject hosts that are too expensive
   143  	if host.StoragePrice.Cmp(maxStoragePrice) > 0 {
   144  		return modules.RenterContract{}, errTooExpensive
   145  	}
   146  	// cap host.MaxCollateral
   147  	if host.MaxCollateral.Cmp(maxCollateral) > 0 {
   148  		host.MaxCollateral = maxCollateral
   149  	}
   150  
   151  	// get an address to use for negotiation
   152  	uc, err := c.wallet.NextAddress()
   153  	if err != nil {
   154  		return modules.RenterContract{}, err
   155  	}
   156  
   157  	// create contract params
   158  	c.mu.RLock()
   159  	params := proto.ContractParams{
   160  		Host:          host,
   161  		Funding:       contractFunding,
   162  		StartHeight:   c.blockHeight,
   163  		EndHeight:     endHeight,
   164  		RefundAddress: uc.UnlockHash(),
   165  	}
   166  	c.mu.RUnlock()
   167  
   168  	// create transaction builder
   169  	txnBuilder := c.wallet.StartTransaction()
   170  
   171  	contract, err := c.contracts.FormContract(params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan())
   172  	if err != nil {
   173  		txnBuilder.Drop()
   174  		return modules.RenterContract{}, err
   175  	}
   176  
   177  	contractValue := contract.RenterFunds
   178  	c.log.Printf("Formed contract with %v for %v", host.NetAddress, contractValue.HumanString())
   179  	return contract, nil
   180  }
   181  
   182  // managedRenew negotiates a new contract for data already stored with a host.
   183  // It returns the new contract. This is a blocking call that performs network
   184  // I/O.
   185  func (c *Contractor) managedRenew(sc *proto.SafeContract, contractFunding types.Currency, newEndHeight types.BlockHeight) (modules.RenterContract, error) {
   186  	// For convenience
   187  	contract := sc.Metadata()
   188  	// Sanity check - should not be renewing a bad contract.
   189  	c.mu.RLock()
   190  	utility := c.contractUtilities[contract.ID]
   191  	c.mu.RUnlock()
   192  	if !utility.GoodForRenew {
   193  		c.log.Critical("Renewing a contract that has been marked as !GoodForRenew")
   194  	}
   195  
   196  	// Fetch the host associated with this contract.
   197  	host, ok := c.hdb.Host(contract.HostPublicKey)
   198  	if !ok {
   199  		return modules.RenterContract{}, errors.New("no record of that host")
   200  	} else if host.StoragePrice.Cmp(maxStoragePrice) > 0 {
   201  		return modules.RenterContract{}, errTooExpensive
   202  	}
   203  	// cap host.MaxCollateral
   204  	if host.MaxCollateral.Cmp(maxCollateral) > 0 {
   205  		host.MaxCollateral = maxCollateral
   206  	}
   207  
   208  	// get an address to use for negotiation
   209  	uc, err := c.wallet.NextAddress()
   210  	if err != nil {
   211  		return modules.RenterContract{}, err
   212  	}
   213  
   214  	// create contract params
   215  	c.mu.RLock()
   216  	params := proto.ContractParams{
   217  		Host:          host,
   218  		Funding:       contractFunding,
   219  		StartHeight:   c.blockHeight,
   220  		EndHeight:     newEndHeight,
   221  		RefundAddress: uc.UnlockHash(),
   222  	}
   223  	c.mu.RUnlock()
   224  
   225  	// execute negotiation protocol
   226  	txnBuilder := c.wallet.StartTransaction()
   227  	newContract, err := c.contracts.Renew(sc, params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan())
   228  	if err != nil {
   229  		txnBuilder.Drop() // return unused outputs to wallet
   230  		return modules.RenterContract{}, err
   231  	}
   232  
   233  	return newContract, nil
   234  }
   235  
   236  // threadedContractMaintenance checks the set of contracts that the contractor
   237  // has against the allownace, renewing any contracts that need to be renewed,
   238  // dropping contracts which are no longer worthwhile, and adding contracts if
   239  // there are not enough.
   240  //
   241  // Between each network call, the thread checks whether a maintenance iterrupt
   242  // signal is being sent. If so, maintannce returns, yielding to whatever thread
   243  // issued the interrupt.
   244  func (c *Contractor) threadedContractMaintenance() {
   245  	// Threading protection.
   246  	err := c.tg.Add()
   247  	if err != nil {
   248  		return
   249  	}
   250  	defer c.tg.Done()
   251  	// Nothing to do if there are no hosts.
   252  	c.mu.RLock()
   253  	wantedHosts := c.allowance.Hosts
   254  	c.mu.RUnlock()
   255  	if wantedHosts <= 0 {
   256  		return
   257  	}
   258  	// Only one instance of this thread should be running at a time. Under
   259  	// normal conditions, fine to return early if another thread is already
   260  	// doing maintenance. The next block will trigger another round. Under
   261  	// testing, control is insufficient if the maintenance loop isn't guaranteed
   262  	// to run.
   263  	if build.Release == "testing" {
   264  		c.maintenanceLock.Lock()
   265  	} else if !c.maintenanceLock.TryLock() {
   266  		return
   267  	}
   268  	defer c.maintenanceLock.Unlock()
   269  
   270  	// Update the utility fields for this contract based on the most recent
   271  	// hostdb.
   272  	c.managedMarkContractsUtility()
   273  
   274  	// Figure out which contracts need to be renewed, and while we have the
   275  	// lock, figure out the end height for the new contracts and also the amount
   276  	// to spend on each contract.
   277  	//
   278  	// refreshSet is used to mark contracts that need to be refreshed because
   279  	// they have run out of money. The refreshSet indicates how much currency
   280  	// was used previously in the contract line, and is used to figure out how
   281  	// much additional money to add in the refreshed contract.
   282  	//
   283  	// The actions inside this RLock are complex enough to merit wrapping them
   284  	// in a function where we can defer the unlock.
   285  	type renewal struct {
   286  		id     types.FileContractID
   287  		amount types.Currency
   288  	}
   289  	var endHeight types.BlockHeight
   290  	var fundsAvailable types.Currency
   291  	var renewSet []renewal
   292  	refreshSet := make(map[types.FileContractID]struct{})
   293  	func() {
   294  		c.mu.RLock()
   295  		defer c.mu.RUnlock()
   296  
   297  		// Grab the end height that should be used for the contracts.
   298  		endHeight = c.currentPeriod + c.allowance.Period
   299  
   300  		// Determine how many funds have been used already in this billing
   301  		// cycle, and how many funds are remaining. We have to calculate these
   302  		// numbers separately to avoid underflow, and then re-join them later to
   303  		// get the full picture for how many funds are available.
   304  		var fundsUsed types.Currency
   305  		for _, contract := range c.contracts.ViewAll() {
   306  			// Calculate the cost of the contract line.
   307  			contractLineCost := contract.TotalCost
   308  			// TODO: add previous contracts here
   309  
   310  			// Check if the contract is expiring. The funds in the contract are
   311  			// handled differently based on this information.
   312  			if c.blockHeight+c.allowance.RenewWindow >= contract.EndHeight {
   313  				// The contract is expiring. Some of the funds are locked down
   314  				// to renew the contract, and then the remaining funds can be
   315  				// allocated to 'availableFunds'.
   316  				fundsUsed = fundsUsed.Add(contractLineCost).Sub(contract.RenterFunds)
   317  				fundsAvailable = fundsAvailable.Add(contract.RenterFunds)
   318  			} else {
   319  				// The contract is not expiring. None of the funds in the
   320  				// contract are available to renew or form contracts.
   321  				fundsUsed = fundsUsed.Add(contractLineCost)
   322  			}
   323  		}
   324  
   325  		// Add any unspent funds from the allowance to the available funds. If
   326  		// the allowance has been decreased, it's possible that we actually need
   327  		// to reduce the number of funds available to compensate.
   328  		if fundsAvailable.Add(c.allowance.Funds).Cmp(fundsUsed) > 0 {
   329  			fundsAvailable = fundsAvailable.Add(c.allowance.Funds).Sub(fundsUsed)
   330  		} else {
   331  			// Figure out how much we need to remove from fundsAvailable to
   332  			// clear the allowance.
   333  			overspend := fundsUsed.Sub(c.allowance.Funds).Sub(fundsAvailable)
   334  			if fundsAvailable.Cmp(overspend) > 0 {
   335  				// We still have some funds available.
   336  				fundsAvailable = fundsAvailable.Sub(overspend)
   337  			} else {
   338  				// The overspend exceeds the available funds, set available
   339  				// funds to zero.
   340  				fundsAvailable = types.ZeroCurrency
   341  			}
   342  		}
   343  
   344  		// Iterate through the contracts again, figuring out which contracts to
   345  		// renew and how much extra funds to renew them with.
   346  		for _, contract := range c.contracts.ViewAll() {
   347  			if !c.contractUtilities[contract.ID].GoodForRenew {
   348  				continue
   349  			}
   350  			if c.blockHeight+c.allowance.RenewWindow >= contract.EndHeight {
   351  				// This contract needs to be renewed because it is going to
   352  				// expire soon. First step is to calculate how much money should
   353  				// be used in the renewal, based on how much of the contract
   354  				// funds (including previous contracts this billing cycle due to
   355  				// financial resets) were spent throughout this billing cycle.
   356  				//
   357  				// The amount we care about is the total amount that was spent
   358  				// on uploading, downloading, and storage throughout the billing
   359  				// cycle. This is calculated by starting with the total cost and
   360  				// subtracting out all of the fees, and then all of the unused
   361  				// money that was allocated (the RenterFunds).
   362  				renewAmount := contract.TotalCost.Sub(contract.ContractFee).Sub(contract.TxnFee).Sub(contract.SiafundFee).Sub(contract.RenterFunds)
   363  				// TODO: add previous contracts here
   364  
   365  				// Get an estimate for how much the fees will cost.
   366  				//
   367  				// TODO: Look up this host in the hostdb to figure out what the
   368  				// actual fees will be.
   369  				estimatedFees := contract.ContractFee.Add(contract.TxnFee).Add(contract.SiafundFee)
   370  				renewAmount = renewAmount.Add(estimatedFees)
   371  
   372  				// Determine if there is enough funds available to suppliement
   373  				// with a 33% bonus, and if there is, add a 33% bonus.
   374  				moneyBuffer := renewAmount.Div64(3)
   375  				if moneyBuffer.Cmp(fundsAvailable) < 0 {
   376  					renewAmount = renewAmount.Add(moneyBuffer)
   377  					fundsAvailable = fundsAvailable.Sub(moneyBuffer)
   378  				} else {
   379  					c.log.Println("WARN: performing a limited renew due to low allowance")
   380  				}
   381  
   382  				// The contract needs to be renewed because it is going to
   383  				// expire soon, and we need to refresh the time.
   384  				renewSet = append(renewSet, renewal{
   385  					id:     contract.ID,
   386  					amount: renewAmount,
   387  				})
   388  			} else {
   389  				// Check if the contract has exhausted its funding and requires
   390  				// premature renewal.
   391  				c.mu.RUnlock()
   392  				host, _ := c.hdb.Host(contract.HostPublicKey)
   393  				c.mu.RLock()
   394  
   395  				// Skip this host if its prices are too high.
   396  				// managedMarkContractsUtility should make this redundant, but
   397  				// this is here for extra safety.
   398  				if host.StoragePrice.Cmp(maxStoragePrice) > 0 || host.UploadBandwidthPrice.Cmp(maxUploadPrice) > 0 {
   399  					continue
   400  				}
   401  
   402  				blockBytes := types.NewCurrency64(modules.SectorSize * uint64(contract.EndHeight-c.blockHeight))
   403  				sectorStoragePrice := host.StoragePrice.Mul(blockBytes)
   404  				sectorBandwidthPrice := host.UploadBandwidthPrice.Mul64(modules.SectorSize)
   405  				sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice)
   406  				percentRemaining, _ := big.NewRat(0, 1).SetFrac(contract.RenterFunds.Big(), contract.TotalCost.Big()).Float64()
   407  				if contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0 || percentRemaining < minContractFundRenewalThreshold {
   408  					// This contract does need to be refreshed. Make sure there
   409  					// are enough funds available to perform the refresh, and
   410  					// then execute.
   411  					refreshAmount := contract.TotalCost.Mul64(2)
   412  					if refreshAmount.Cmp(fundsAvailable) < 0 {
   413  						refreshSet[contract.ID] = struct{}{}
   414  						renewSet = append(renewSet, renewal{
   415  							id:     contract.ID,
   416  							amount: refreshAmount,
   417  						})
   418  					} else {
   419  						c.log.Println("WARN: cannot refresh empty contract due to low allowance.")
   420  					}
   421  				}
   422  			}
   423  		}
   424  	}()
   425  	if len(renewSet) != 0 {
   426  		c.log.Printf("renewing %v contracts", len(renewSet))
   427  	}
   428  
   429  	// Loop through the contracts and renew them one-by-one.
   430  	for _, renewal := range renewSet {
   431  		// Pull the variables out of the renewal.
   432  		id := renewal.id
   433  		amount := renewal.amount
   434  
   435  		// Renew one contract.
   436  		func() {
   437  			// Mark the contract as being renewed, and defer logic to unmark it
   438  			// once renewing is complete.
   439  			c.mu.Lock()
   440  			c.renewing[id] = true
   441  			c.mu.Unlock()
   442  			defer func() {
   443  				c.mu.Lock()
   444  				delete(c.renewing, id)
   445  				c.mu.Unlock()
   446  			}()
   447  
   448  			// Wait for any active editors and downloaders to finish for this
   449  			// contract, and then grab the latest revision.
   450  			c.mu.RLock()
   451  			e, eok := c.editors[id]
   452  			d, dok := c.downloaders[id]
   453  			c.mu.RUnlock()
   454  			if eok {
   455  				e.invalidate()
   456  			}
   457  			if dok {
   458  				d.invalidate()
   459  			}
   460  
   461  			// Fetch the contract that we are renewing.
   462  			oldContract, exists := c.contracts.Acquire(id)
   463  			if !exists {
   464  				return
   465  			}
   466  			// Return the contract if it's not useful for renewing.
   467  			c.mu.RLock()
   468  			oldUtility := c.contractUtilities[id]
   469  			c.mu.RUnlock()
   470  			if !oldUtility.GoodForRenew {
   471  				c.log.Printf("Contract %v slated for renew is marked not good for renew", id)
   472  				c.contracts.Return(oldContract)
   473  				return
   474  			}
   475  			// Perform the actual renew. If the renew fails, return the
   476  			// contract.
   477  			newContract, err := c.managedRenew(oldContract, amount, endHeight)
   478  			if err != nil {
   479  				c.log.Printf("WARN: failed to renew contract %v: %v\n", id, err)
   480  				c.contracts.Return(oldContract)
   481  				return
   482  			}
   483  			c.log.Printf("Renewed contract %v\n", id)
   484  
   485  			// Update the utility values for the new contract, and for the old
   486  			// contract.
   487  			c.mu.Lock()
   488  			newUtility := modules.ContractUtility{
   489  				GoodForUpload: true,
   490  				GoodForRenew:  true,
   491  			}
   492  			c.contractUtilities[newContract.ID] = newUtility
   493  			oldUtility.GoodForRenew = false
   494  			oldUtility.GoodForUpload = false
   495  			c.contractUtilities[id] = oldUtility
   496  			c.mu.Unlock()
   497  			// If the contract is a mid-cycle renew, add the contract line to
   498  			// the new contract. The contract line is not included/extended if
   499  			// we are just renewing because the contract is expiring.
   500  			if _, exists := refreshSet[id]; exists {
   501  				// TODO: update PreviousContracts
   502  			}
   503  
   504  			// Lock the contractor as we update it to use the new contract
   505  			// instead of the old contract.
   506  			c.mu.Lock()
   507  			defer c.mu.Unlock()
   508  			// Delete the old contract.
   509  			c.contracts.Delete(oldContract)
   510  			// Store the contract in the record of historic contracts.
   511  			c.oldContracts[id] = oldContract.Metadata()
   512  			// Add a mapping from the old contract to the new contract.
   513  			c.renewedIDs[id] = newContract.ID
   514  			// Save the contractor.
   515  			err = c.saveSync()
   516  			if err != nil {
   517  				c.log.Println("Failed to save the contractor after creating a new contract.")
   518  			}
   519  		}()
   520  
   521  		// Soft sleep for a minute to allow all of the transactions to propagate
   522  		// the network.
   523  		select {
   524  		case <-c.tg.StopChan():
   525  			return
   526  		case <-c.interruptMaintenance:
   527  			return
   528  		case <-time.After(contractFormationInterval):
   529  		}
   530  	}
   531  
   532  	// Quit in the event of shutdown.
   533  	select {
   534  	case <-c.tg.StopChan():
   535  		return
   536  	case <-c.interruptMaintenance:
   537  		return
   538  	default:
   539  	}
   540  
   541  	// Count the number of contracts which are good for uploading, and then make
   542  	// more as needed to fill the gap.
   543  	c.mu.RLock()
   544  	uploadContracts := 0
   545  	for _, id := range c.contracts.IDs() {
   546  		if c.contractUtilities[id].GoodForUpload {
   547  			uploadContracts++
   548  		}
   549  	}
   550  	neededContracts := int(c.allowance.Hosts) - uploadContracts
   551  	c.mu.RUnlock()
   552  	if neededContracts <= 0 {
   553  		return
   554  	}
   555  
   556  	// Assemble an exclusion list that includes all of the hosts that we already
   557  	// have contracts with, then select a new batch of hosts to attempt contract
   558  	// formation with.
   559  	c.mu.RLock()
   560  	var exclude []types.SiaPublicKey
   561  	for _, contract := range c.contracts.ViewAll() {
   562  		exclude = append(exclude, contract.HostPublicKey)
   563  	}
   564  	initialContractFunds := c.allowance.Funds.Div64(c.allowance.Hosts).Div64(3)
   565  	c.mu.RUnlock()
   566  	hosts := c.hdb.RandomHosts(neededContracts*2+10, exclude)
   567  
   568  	// Form contracts with the hosts one at a time, until we have enough
   569  	// contracts.
   570  	for _, host := range hosts {
   571  		// Determine if we have enough money to form a new contract.
   572  		if fundsAvailable.Cmp(initialContractFunds) < 0 {
   573  			c.log.Println("WARN: need to form new contracts, but unable to because of a low allowance")
   574  			break
   575  		}
   576  
   577  		// Attempt forming a contract with this host.
   578  		newContract, err := c.managedNewContract(host, initialContractFunds, endHeight)
   579  		if err != nil {
   580  			c.log.Printf("Attempted to form a contract with %v, but negotiation failed: %v\n", host.NetAddress, err)
   581  			continue
   582  		}
   583  
   584  		// Add this contract to the contractor and save.
   585  		c.mu.Lock()
   586  		c.contractUtilities[newContract.ID] = modules.ContractUtility{
   587  			GoodForUpload: true,
   588  			GoodForRenew:  true,
   589  		}
   590  		err = c.saveSync()
   591  		c.mu.Unlock()
   592  		if err != nil {
   593  			c.log.Println("Unable to save the contractor:", err)
   594  		}
   595  
   596  		// Quit the loop if we've replaced all needed contracts.
   597  		neededContracts--
   598  		if neededContracts <= 0 {
   599  			break
   600  		}
   601  
   602  		// Soft sleep before making the next contract.
   603  		select {
   604  		case <-c.tg.StopChan():
   605  			return
   606  		case <-c.interruptMaintenance:
   607  			return
   608  		case <-time.After(contractFormationInterval):
   609  		}
   610  	}
   611  }