gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/contractor.go (about)

     1  package contractor
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  	"sync/atomic"
    13  
    14  	"gitlab.com/NebulousLabs/errors"
    15  	"gitlab.com/NebulousLabs/ratelimit"
    16  	"gitlab.com/NebulousLabs/threadgroup"
    17  
    18  	"gitlab.com/SkynetLabs/skyd/build"
    19  	"gitlab.com/SkynetLabs/skyd/skymodules"
    20  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/proto"
    21  	"go.sia.tech/siad/crypto"
    22  	"go.sia.tech/siad/modules"
    23  	"go.sia.tech/siad/persist"
    24  	siasync "go.sia.tech/siad/sync"
    25  	"go.sia.tech/siad/types"
    26  )
    27  
    28  var (
    29  	errNilCS     = errors.New("cannot create contractor with nil consensus set")
    30  	errNilHDB    = errors.New("cannot create contractor with nil HostDB")
    31  	errNilTpool  = errors.New("cannot create contractor with nil transaction pool")
    32  	errNilWallet = errors.New("cannot create contractor with nil wallet")
    33  
    34  	errHostNotFound     = errors.New("host not found")
    35  	errContractNotFound = errors.New("contract not found")
    36  
    37  	// COMPATv1.0.4-lts
    38  	// metricsContractID identifies a special contract that contains aggregate
    39  	// financial metrics from older contractors
    40  	metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'}
    41  )
    42  
    43  // emptyWorkerPool is the workerpool that a contractor is initialized with.
    44  type emptyWorkerPool struct{}
    45  
    46  // Worker implements the WorkerPool interface.
    47  func (emptyWorkerPool) Worker(_ types.SiaPublicKey) (skymodules.Worker, error) {
    48  	return nil, errors.New("empty worker pool")
    49  }
    50  
    51  // A Contractor negotiates, revises, renews, and provides access to file
    52  // contracts.
    53  type Contractor struct {
    54  	// dependencies
    55  	staticAlerter    *modules.GenericAlerter
    56  	staticCS         modules.ConsensusSet
    57  	staticDeps       modules.Dependencies
    58  	staticHDB        skymodules.HostDB
    59  	staticLog        *persist.Logger
    60  	staticTG         threadgroup.ThreadGroup
    61  	staticTPool      modules.TransactionPool
    62  	staticWallet     modules.Wallet
    63  	staticWorkerPool skymodules.WorkerPool
    64  
    65  	mu         sync.RWMutex
    66  	persistDir string
    67  
    68  	// Only one thread should be performing contract maintenance at a time.
    69  	staticInterruptMaintenance chan struct{}
    70  	maintenanceLock            siasync.TryMutex
    71  
    72  	// Only one thread should be scanning the blockchain for recoverable
    73  	// contracts at a time.
    74  	atomicScanInProgress     uint32
    75  	atomicRecoveryScanHeight int64
    76  
    77  	allowance     skymodules.Allowance
    78  	blockHeight   types.BlockHeight
    79  	synced        chan struct{}
    80  	currentPeriod types.BlockHeight
    81  	lastChange    modules.ConsensusChangeID
    82  
    83  	// recentRecoveryChange is the first ConsensusChange that was missed while
    84  	// trying to find recoverable contracts. This is where we need to start
    85  	// rescanning the blockchain for recoverable contracts the next time the wallet
    86  	// is unlocked.
    87  	recentRecoveryChange modules.ConsensusChangeID
    88  
    89  	downloaders     map[types.FileContractID]*hostDownloader
    90  	editors         map[types.FileContractID]*hostEditor
    91  	sessions        map[types.FileContractID]*hostSession
    92  	numFailedRenews map[types.FileContractID]types.BlockHeight
    93  	renewing        map[types.FileContractID]bool // prevent revising during renewal
    94  
    95  	// pubKeysToContractID is a map of host pubkeys to the latest contract ID
    96  	// that is formed with the host. The contract also has to have an end height
    97  	// in the future
    98  	pubKeysToContractID map[string]types.FileContractID
    99  
   100  	// renewedFrom links the new contract's ID to the old contract's ID
   101  	// renewedTo links the old contract's ID to the new contract's ID
   102  	// doubleSpentContracts keep track of all contracts that were double spent by
   103  	// either the renter or host.
   104  	staticContracts      *proto.ContractSet
   105  	oldContracts         map[types.FileContractID]skymodules.RenterContract
   106  	preferredHosts       map[string]struct{}
   107  	doubleSpentContracts map[types.FileContractID]types.BlockHeight
   108  	recoverableContracts map[types.FileContractID]skymodules.RecoverableContract
   109  	renewedFrom          map[types.FileContractID]types.FileContractID
   110  	renewedTo            map[types.FileContractID]types.FileContractID
   111  
   112  	staticChurnLimiter *churnLimiter
   113  	staticWatchdog     *watchdog
   114  }
   115  
   116  // PaymentDetails is a helper struct that contains extra information on a
   117  // payment. Most notably it includes a breakdown of the spending details for a
   118  // payment, the contractor uses this information to update its spending details
   119  // accordingly.
   120  type PaymentDetails struct {
   121  	// destination details
   122  	Host types.SiaPublicKey
   123  
   124  	// payment details
   125  	Amount        types.Currency
   126  	RefundAccount modules.AccountID
   127  
   128  	// spending details
   129  	SpendingDetails skymodules.SpendingDetails
   130  }
   131  
   132  // Allowance returns the current allowance.
   133  func (c *Contractor) Allowance() skymodules.Allowance {
   134  	c.mu.RLock()
   135  	defer c.mu.RUnlock()
   136  	return c.allowance
   137  }
   138  
   139  // ContractPublicKey returns the public key capable of verifying the renter's
   140  // signature on a contract.
   141  func (c *Contractor) ContractPublicKey(pk types.SiaPublicKey) (crypto.PublicKey, bool) {
   142  	c.mu.RLock()
   143  	id, ok := c.pubKeysToContractID[pk.String()]
   144  	c.mu.RUnlock()
   145  	if !ok {
   146  		return crypto.PublicKey{}, false
   147  	}
   148  	return c.staticContracts.PublicKey(id)
   149  }
   150  
   151  // InitRecoveryScan starts scanning the whole blockchain for recoverable
   152  // contracts within a separate thread.
   153  func (c *Contractor) InitRecoveryScan() (err error) {
   154  	if err := c.staticTG.Add(); err != nil {
   155  		return err
   156  	}
   157  	defer c.staticTG.Done()
   158  	return c.callInitRecoveryScan(modules.ConsensusChangeBeginning)
   159  }
   160  
   161  // PeriodSpending returns the amount spent on contracts during the current
   162  // billing period.
   163  func (c *Contractor) PeriodSpending() (skymodules.ContractorSpending, error) {
   164  	allContracts := c.staticContracts.ViewAll()
   165  	c.mu.RLock()
   166  	defer c.mu.RUnlock()
   167  
   168  	var spending skymodules.ContractorSpending
   169  	for _, contract := range allContracts {
   170  		// Don't count double-spent contracts.
   171  		if _, doubleSpent := c.doubleSpentContracts[contract.ID]; doubleSpent {
   172  			continue
   173  		}
   174  
   175  		// Calculate ContractFees
   176  		spending.Fees.ContractFees = spending.Fees.ContractFees.Add(contract.ContractFee)
   177  		spending.Fees.SiafundFees = spending.Fees.SiafundFees.Add(contract.SiafundFee)
   178  		spending.Fees.TransactionFees = spending.Fees.TransactionFees.Add(contract.TxnFee)
   179  		// Calculate TotalAllocated
   180  		spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost)
   181  		spending.ContractSpendingDeprecated = spending.TotalAllocated
   182  		// Calculate Spending
   183  		spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
   184  		spending.FundAccountSpending = spending.FundAccountSpending.Add(contract.FundAccountSpending)
   185  		spending.MaintenanceSpending = spending.MaintenanceSpending.Add(contract.MaintenanceSpending)
   186  		spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
   187  		spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
   188  	}
   189  
   190  	// Calculate needed spending to be reported from old contracts
   191  	for _, contract := range c.oldContracts {
   192  		// Don't count double-spent contracts.
   193  		if _, doubleSpent := c.doubleSpentContracts[contract.ID]; doubleSpent {
   194  			continue
   195  		}
   196  
   197  		host, exist, err := c.staticHDB.Host(contract.HostPublicKey)
   198  		if contract.StartHeight >= c.currentPeriod {
   199  			// Calculate spending from contracts that were renewed during the current period
   200  			// Calculate ContractFees
   201  			spending.Fees.ContractFees = spending.Fees.ContractFees.Add(contract.ContractFee)
   202  			spending.Fees.SiafundFees = spending.Fees.SiafundFees.Add(contract.TxnFee)
   203  			spending.Fees.TransactionFees = spending.Fees.TransactionFees.Add(contract.SiafundFee)
   204  			// Calculate TotalAllocated
   205  			spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost)
   206  			// Calculate Spending
   207  			spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
   208  			spending.FundAccountSpending = spending.FundAccountSpending.Add(contract.FundAccountSpending)
   209  			spending.MaintenanceSpending = spending.MaintenanceSpending.Add(contract.MaintenanceSpending)
   210  			spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
   211  			spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
   212  		} else if err != nil && exist && contract.EndHeight+host.WindowSize+types.MaturityDelay > c.blockHeight {
   213  			// Calculate funds that are being withheld in contracts
   214  			spending.WithheldFunds = spending.WithheldFunds.Add(contract.RenterFunds)
   215  			// Record the largest window size for worst case when reporting the spending
   216  			if contract.EndHeight+host.WindowSize+types.MaturityDelay >= spending.ReleaseBlock {
   217  				spending.ReleaseBlock = contract.EndHeight + host.WindowSize + types.MaturityDelay
   218  			}
   219  			// Calculate Previous spending
   220  			spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee).
   221  				Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending).Add(contract.FundAccountSpending).Add(contract.MaintenanceSpending.Sum())
   222  		} else {
   223  			// Calculate Previous spending
   224  			spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee).
   225  				Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending).Add(contract.FundAccountSpending).Add(contract.MaintenanceSpending.Sum())
   226  		}
   227  	}
   228  
   229  	// Calculate amount of spent money to get unspent money.
   230  	allSpending := spending.Fees.Sum()
   231  	allSpending = allSpending.Add(spending.DownloadSpending)
   232  	allSpending = allSpending.Add(spending.UploadSpending)
   233  	allSpending = allSpending.Add(spending.StorageSpending)
   234  	allSpending = allSpending.Add(spending.FundAccountSpending)
   235  	allSpending = allSpending.Add(spending.MaintenanceSpending.Sum())
   236  	if c.allowance.Funds.Cmp(allSpending) >= 0 {
   237  		spending.Unspent = c.allowance.Funds.Sub(allSpending)
   238  	}
   239  
   240  	return spending, nil
   241  }
   242  
   243  // CurrentPeriod returns the height at which the current allowance period
   244  // began.
   245  func (c *Contractor) CurrentPeriod() types.BlockHeight {
   246  	c.mu.RLock()
   247  	defer c.mu.RUnlock()
   248  	return c.currentPeriod
   249  }
   250  
   251  // UpdateWorkerPool updates the workerpool currently in use by the contractor.
   252  func (c *Contractor) UpdateWorkerPool(wp skymodules.WorkerPool) {
   253  	c.mu.Lock()
   254  	defer c.mu.Unlock()
   255  	c.staticWorkerPool = wp
   256  }
   257  
   258  // ProvidePayment takes a stream and a set of payment details and handles
   259  // the payment for an RPC by sending and processing payment request and
   260  // response objects to the host. It returns an error in case of failure.
   261  func (c *Contractor) ProvidePayment(stream io.ReadWriter, pt *modules.RPCPriceTable, details PaymentDetails) error {
   262  	// convenience variables
   263  	host := details.Host
   264  	refundAccount := details.RefundAccount
   265  	amount := details.Amount
   266  	bh := pt.HostBlockHeight
   267  
   268  	// find a contract for the given host
   269  	contract, exists := c.ContractByPublicKey(host)
   270  	if !exists {
   271  		return errContractNotFound
   272  	}
   273  
   274  	// acquire a safe contract
   275  	sc, exists := c.staticContracts.Acquire(contract.ID)
   276  	if !exists {
   277  		return errContractNotFound
   278  	}
   279  	defer c.staticContracts.Return(sc)
   280  
   281  	// create a new revision
   282  	current := sc.LastRevision()
   283  	rev, err := current.EAFundRevision(amount)
   284  	if err != nil {
   285  		return errors.AddContext(err, "Failed to create a payment revision")
   286  	}
   287  
   288  	// create transaction containing the revision
   289  	signedTxn := rev.ToTransaction()
   290  	sig := sc.Sign(signedTxn.SigHash(0, bh))
   291  	signedTxn.TransactionSignatures[0].Signature = sig[:]
   292  
   293  	// record the payment intent
   294  	walTxn, err := sc.RecordPaymentIntent(rev, amount, details.SpendingDetails)
   295  	if err != nil {
   296  		return errors.AddContext(err, "Failed to record payment intent")
   297  	}
   298  
   299  	// prepare a buffer so we can optimize our writes
   300  	buffer := bytes.NewBuffer(nil)
   301  
   302  	// send PaymentRequest
   303  	err = modules.RPCWrite(buffer, modules.PaymentRequest{Type: modules.PayByContract})
   304  	if err != nil {
   305  		return errors.AddContext(err, "unable to write payment request to host")
   306  	}
   307  
   308  	// send PayByContractRequest
   309  	err = modules.RPCWrite(buffer, newPayByContractRequest(rev, sig, refundAccount))
   310  	if err != nil {
   311  		return errors.AddContext(err, "unable to write the pay by contract request")
   312  	}
   313  
   314  	// write contents of the buffer to the stream
   315  	_, err = stream.Write(buffer.Bytes())
   316  	if err != nil {
   317  		return errors.AddContext(err, "could not write the buffer contents")
   318  	}
   319  
   320  	// receive PayByContractResponse
   321  	var payByResponse modules.PayByContractResponse
   322  	if err := modules.RPCRead(stream, &payByResponse); err != nil {
   323  		if strings.Contains(err.Error(), "storage obligation not found") {
   324  			c.staticLog.Printf("Marking contract %v as bad because host %v did not recognize it: %v", contract.ID, host, err)
   325  			mbcErr := c.managedMarkContractBad(sc)
   326  			if mbcErr != nil {
   327  				c.staticLog.Printf("Unable to mark contract %v on host %v as bad: %v", contract.ID, host, mbcErr)
   328  			}
   329  		}
   330  		return errors.AddContext(err, "unable to read the pay by contract response")
   331  	}
   332  
   333  	// TODO: Check for revision mismatch and recover by applying the contract
   334  	// unapplied transactions and trying again.
   335  
   336  	// verify the host's signature
   337  	hash := crypto.HashAll(rev)
   338  	hpk := sc.Metadata().HostPublicKey
   339  	err = crypto.VerifyHash(hash, hpk.ToPublicKey(), payByResponse.Signature)
   340  	if err != nil {
   341  		return errors.New("could not verify host's signature")
   342  	}
   343  
   344  	// commit payment intent
   345  	if !c.staticDeps.Disrupt("DisableCommitPaymentIntent") {
   346  		err = sc.CommitPaymentIntent(walTxn, signedTxn, amount, details.SpendingDetails)
   347  		if err != nil {
   348  			return errors.AddContext(err, "Failed to commit unknown spending intent")
   349  		}
   350  	}
   351  
   352  	return nil
   353  }
   354  
   355  // RecoveryScanStatus returns a bool indicating if a scan for recoverable
   356  // contracts is in progress and if it is, the current progress of the scan.
   357  func (c *Contractor) RecoveryScanStatus() (bool, types.BlockHeight) {
   358  	bh := types.BlockHeight(atomic.LoadInt64(&c.atomicRecoveryScanHeight))
   359  	sip := atomic.LoadUint32(&c.atomicScanInProgress)
   360  	return sip == 1, bh
   361  }
   362  
   363  // RefreshedContract returns a bool indicating if the contract was a refreshed
   364  // contract. A refreshed contract refers to a contract that ran out of funds
   365  // prior to the end height and so was renewed with the host in the same period.
   366  // Both the old and the new contract have the same end height
   367  func (c *Contractor) RefreshedContract(fcid types.FileContractID) bool {
   368  	// Add thread and acquire lock
   369  	if err := c.staticTG.Add(); err != nil {
   370  		return false
   371  	}
   372  	defer c.staticTG.Done()
   373  	c.mu.RLock()
   374  	defer c.mu.RUnlock()
   375  
   376  	// Check if contract ID is found in the renewedTo map indicating that the
   377  	// contract was renewed
   378  	newFCID, renewed := c.renewedTo[fcid]
   379  	if !renewed {
   380  		return false
   381  	}
   382  
   383  	// Grab the contract to check its end height
   384  	contract, ok := c.oldContracts[fcid]
   385  	if !ok {
   386  		c.staticLog.Debugln("Contract not found in oldContracts, despite there being a renewal to the contract")
   387  		return false
   388  	}
   389  
   390  	// Grab the contract it was renewed to to check its end height
   391  	newContract, ok := c.staticContracts.View(newFCID)
   392  	if !ok {
   393  		newContract, ok = c.oldContracts[newFCID]
   394  		if !ok {
   395  			c.staticLog.Debugln("Contract was not found in the database, despite their being another contract that claims to have renewed to it.")
   396  			return false
   397  		}
   398  	}
   399  
   400  	// The contract was refreshed if the endheights are the same
   401  	return newContract.EndHeight == contract.EndHeight
   402  }
   403  
   404  // Synced returns a channel that is closed when the contractor is synced with
   405  // the peer-to-peer network.
   406  func (c *Contractor) Synced() <-chan struct{} {
   407  	c.mu.RLock()
   408  	defer c.mu.RUnlock()
   409  	return c.synced
   410  }
   411  
   412  // Close closes the Contractor.
   413  func (c *Contractor) Close() error {
   414  	return c.staticTG.Stop()
   415  }
   416  
   417  // newWithDeps returns a new Contractor.
   418  func newWithDeps(cs modules.ConsensusSet, wallet modules.Wallet, tpool modules.TransactionPool, hdb skymodules.HostDB, rl *ratelimit.RateLimit, persistDir string, deps modules.Dependencies) (*Contractor, <-chan error) {
   419  	errChan := make(chan error, 1)
   420  	defer close(errChan)
   421  	// Check for nil inputs.
   422  	if cs == nil {
   423  		errChan <- errNilCS
   424  		return nil, errChan
   425  	}
   426  	if wallet == nil {
   427  		errChan <- errNilWallet
   428  		return nil, errChan
   429  	}
   430  	if tpool == nil {
   431  		errChan <- errNilTpool
   432  		return nil, errChan
   433  	}
   434  	if hdb == nil {
   435  		errChan <- errNilHDB
   436  		return nil, errChan
   437  	}
   438  
   439  	// Create the persist directory if it does not yet exist.
   440  	if err := os.MkdirAll(persistDir, 0700); err != nil {
   441  		errChan <- err
   442  		return nil, errChan
   443  	}
   444  
   445  	// Convert the old persist file(s), if necessary. This must occur before
   446  	// loading the contract set.
   447  	if err := convertPersist(persistDir, rl); err != nil {
   448  		errChan <- err
   449  		return nil, errChan
   450  	}
   451  
   452  	// Create the contract set.
   453  	contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), rl, modules.ProdDependencies)
   454  	if err != nil {
   455  		errChan <- err
   456  		return nil, errChan
   457  	}
   458  	// Create the logger.
   459  	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
   460  	if err != nil {
   461  		errChan <- err
   462  		return nil, errChan
   463  	}
   464  
   465  	// Create Contractor using production dependencies.
   466  	return NewCustomContractor(cs, wallet, tpool, hdb, persistDir, contractSet, logger, deps)
   467  }
   468  
   469  // New returns a new Contractor.
   470  func New(cs modules.ConsensusSet, wallet modules.Wallet, tpool modules.TransactionPool, hdb skymodules.HostDB, rl *ratelimit.RateLimit, persistDir string) (*Contractor, <-chan error) {
   471  	return newWithDeps(cs, wallet, tpool, hdb, rl, persistDir, modules.ProdDependencies)
   472  }
   473  
   474  // contractorBlockingStartup handles the blocking portion of NewCustomContractor.
   475  func contractorBlockingStartup(cs modules.ConsensusSet, w modules.Wallet, tp modules.TransactionPool, hdb skymodules.HostDB, persistDir string, contractSet *proto.ContractSet, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) {
   476  	// Create the Contractor object.
   477  	c := &Contractor{
   478  		staticAlerter: skymodules.NewAlerter("contractor"),
   479  		staticCS:      cs,
   480  		staticDeps:    deps,
   481  		staticHDB:     hdb,
   482  		staticLog:     l,
   483  		persistDir:    persistDir,
   484  		staticTPool:   tp,
   485  		staticWallet:  w,
   486  
   487  		staticInterruptMaintenance: make(chan struct{}),
   488  		synced:                     make(chan struct{}),
   489  
   490  		staticContracts:      contractSet,
   491  		downloaders:          make(map[types.FileContractID]*hostDownloader),
   492  		editors:              make(map[types.FileContractID]*hostEditor),
   493  		sessions:             make(map[types.FileContractID]*hostSession),
   494  		oldContracts:         make(map[types.FileContractID]skymodules.RenterContract),
   495  		doubleSpentContracts: make(map[types.FileContractID]types.BlockHeight),
   496  		preferredHosts:       make(map[string]struct{}),
   497  		recoverableContracts: make(map[types.FileContractID]skymodules.RecoverableContract),
   498  		renewing:             make(map[types.FileContractID]bool),
   499  		renewedFrom:          make(map[types.FileContractID]types.FileContractID),
   500  		renewedTo:            make(map[types.FileContractID]types.FileContractID),
   501  		staticWorkerPool:     emptyWorkerPool{},
   502  	}
   503  	c.staticChurnLimiter = newChurnLimiter(c)
   504  	c.staticWatchdog = newWatchdog(c)
   505  
   506  	// Close the contract set and logger upon shutdown.
   507  	err := c.staticTG.AfterStop(func() error {
   508  		if err := c.staticContracts.Close(); err != nil {
   509  			return errors.AddContext(err, "failed to close contract set")
   510  		}
   511  		if err := c.staticLog.Close(); err != nil {
   512  			return errors.AddContext(err, "failed to close the contractor logger")
   513  		}
   514  		return nil
   515  	})
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  
   520  	// Load the prior persistence structures.
   521  	err = c.load()
   522  	if err != nil && !os.IsNotExist(err) {
   523  		return nil, err
   524  	}
   525  
   526  	// Update the pubkeyToContractID map
   527  	c.managedUpdatePubKeyToContractIDMap()
   528  
   529  	// Unsubscribe from the consensus set upon shutdown.
   530  	err = c.staticTG.OnStop(func() error {
   531  		cs.Unsubscribe(c)
   532  		return nil
   533  	})
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  
   538  	// We may have upgraded persist or resubscribed. Save now so that we don't
   539  	// lose our work.
   540  	c.mu.Lock()
   541  	err = c.save()
   542  	c.mu.Unlock()
   543  	if err != nil {
   544  		return nil, err
   545  	}
   546  
   547  	// Update the allowance in the hostdb with the one that was loaded from
   548  	// disk.
   549  	err = c.staticHDB.SetAllowance(c.allowance)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	return c, nil
   554  }
   555  
   556  // contractorAsyncStartup handles the async portion of NewCustomContractor.
   557  func contractorAsyncStartup(c *Contractor, cs modules.ConsensusSet) error {
   558  	if c.staticDeps.Disrupt("BlockAsyncStartup") {
   559  		return nil
   560  	}
   561  	err := cs.ConsensusSetSubscribe(c, c.lastChange, c.staticTG.StopChan())
   562  	if errors.Contains(err, modules.ErrInvalidConsensusChangeID) {
   563  		// Reset the contractor consensus variables and try rescanning.
   564  		c.blockHeight = 0
   565  		c.lastChange = modules.ConsensusChangeBeginning
   566  		c.recentRecoveryChange = modules.ConsensusChangeBeginning
   567  		err = cs.ConsensusSetSubscribe(c, c.lastChange, c.staticTG.StopChan())
   568  	}
   569  	if err != nil && strings.Contains(err.Error(), threadgroup.ErrStopped.Error()) {
   570  		return nil
   571  	}
   572  	if err != nil {
   573  		return err
   574  	}
   575  	return nil
   576  }
   577  
   578  // NewCustomContractor creates a Contractor using the provided dependencies.
   579  func NewCustomContractor(cs modules.ConsensusSet, w modules.Wallet, tp modules.TransactionPool, hdb skymodules.HostDB, persistDir string, contractSet *proto.ContractSet, l *persist.Logger, deps modules.Dependencies) (*Contractor, <-chan error) {
   580  	errChan := make(chan error, 1)
   581  
   582  	// Handle blocking startup.
   583  	c, err := contractorBlockingStartup(cs, w, tp, hdb, persistDir, contractSet, l, deps)
   584  	if err != nil {
   585  		errChan <- err
   586  		return nil, errChan
   587  	}
   588  
   589  	// non-blocking startup.
   590  	go func() {
   591  		// Subscribe to the consensus set in a separate goroutine.
   592  		defer close(errChan)
   593  		if err := c.staticTG.Add(); err != nil {
   594  			errChan <- err
   595  			return
   596  		}
   597  		defer c.staticTG.Done()
   598  		err := contractorAsyncStartup(c, cs)
   599  		if err != nil {
   600  			errChan <- err
   601  		}
   602  	}()
   603  	return c, errChan
   604  }
   605  
   606  // callInitRecoveryScan starts scanning the whole blockchain at a certain
   607  // ChangeID for recoverable contracts within a separate thread.
   608  func (c *Contractor) callInitRecoveryScan(scanStart modules.ConsensusChangeID) (err error) {
   609  	// Check if we are already scanning the blockchain.
   610  	if !atomic.CompareAndSwapUint32(&c.atomicScanInProgress, 0, 1) {
   611  		return errors.New("scan for recoverable contracts is already in progress")
   612  	}
   613  	// Reset the progress and status if there was an error.
   614  	defer func() {
   615  		if err != nil {
   616  			atomic.StoreUint32(&c.atomicScanInProgress, 0)
   617  			atomic.StoreInt64(&c.atomicRecoveryScanHeight, 0)
   618  		}
   619  	}()
   620  	// Get the wallet seed.
   621  	s, _, err := c.staticWallet.PrimarySeed()
   622  	if err != nil {
   623  		return errors.AddContext(err, "failed to get wallet seed")
   624  	}
   625  	// Get the renter seed and wipe it once done.
   626  	rs := skymodules.DeriveRenterSeed(s)
   627  	// Reset the scan progress before starting the scan.
   628  	atomic.StoreInt64(&c.atomicRecoveryScanHeight, 0)
   629  	// Create the scanner.
   630  	scanner := newRecoveryScanner(c, rs)
   631  	// Start the scan.
   632  	go func() {
   633  		// Add scanning thread to threadgroup.
   634  		if err := c.staticTG.Add(); err != nil {
   635  			return
   636  		}
   637  		defer c.staticTG.Done()
   638  		// Scan blockchain.
   639  		if err := scanner.threadedScan(c.staticCS, scanStart, c.staticTG.StopChan()); err != nil {
   640  			c.staticLog.Println("Scan failed", err)
   641  		}
   642  		if c.staticDeps.Disrupt("disableRecoveryStatusReset") {
   643  			return
   644  		}
   645  		// Reset the scan related fields.
   646  		if !atomic.CompareAndSwapUint32(&c.atomicScanInProgress, 1, 0) {
   647  			build.Critical("finished recovery scan but scanInProgress was already set to 0")
   648  		}
   649  		atomic.StoreInt64(&c.atomicRecoveryScanHeight, 0)
   650  		// Save the renter.
   651  		c.mu.Lock()
   652  		c.save()
   653  		c.mu.Unlock()
   654  	}()
   655  	return nil
   656  }
   657  
   658  // managedSynced returns true if the contractor is synced with the consensusset.
   659  func (c *Contractor) managedSynced() bool {
   660  	c.mu.RLock()
   661  	defer c.mu.RUnlock()
   662  	select {
   663  	case <-c.synced:
   664  		return true
   665  	default:
   666  	}
   667  	return false
   668  }
   669  
   670  // newPayByContractRequest is a helper function that takes a revision,
   671  // signature and refund account and creates a PayByContractRequest object.
   672  func newPayByContractRequest(rev types.FileContractRevision, sig crypto.Signature, refundAccount modules.AccountID) modules.PayByContractRequest {
   673  	req := modules.PayByContractRequest{
   674  		ContractID:           rev.ID(),
   675  		NewRevisionNumber:    rev.NewRevisionNumber,
   676  		NewValidProofValues:  make([]types.Currency, len(rev.NewValidProofOutputs)),
   677  		NewMissedProofValues: make([]types.Currency, len(rev.NewMissedProofOutputs)),
   678  		RefundAccount:        refundAccount,
   679  		Signature:            sig[:],
   680  	}
   681  	for i, o := range rev.NewValidProofOutputs {
   682  		req.NewValidProofValues[i] = o.Value
   683  	}
   684  	for i, o := range rev.NewMissedProofOutputs {
   685  		req.NewMissedProofValues[i] = o.Value
   686  	}
   687  	return req
   688  }
   689  
   690  // RenewContract takes an established connection to a host and renews the
   691  // contract with that host.
   692  func (c *Contractor) RenewContract(conn net.Conn, fcid types.FileContractID, params skymodules.ContractParams, txnBuilder modules.TransactionBuilder, tpool modules.TransactionPool, hdb skymodules.HostDB, pt *modules.RPCPriceTable) (skymodules.RenterContract, []types.Transaction, error) {
   693  	newContract, txnSet, err := c.staticContracts.RenewContract(conn, fcid, params, txnBuilder, tpool, hdb, pt)
   694  	if err != nil {
   695  		return skymodules.RenterContract{}, nil, errors.AddContext(err, "RenewContract: failed to renew contract")
   696  	}
   697  	// Update various mappings in the contractor after a successful renewal.
   698  	c.mu.Lock()
   699  	c.renewedFrom[newContract.ID] = fcid
   700  	c.renewedTo[fcid] = newContract.ID
   701  	c.pubKeysToContractID[newContract.HostPublicKey.String()] = newContract.ID
   702  	c.mu.Unlock()
   703  	return newContract, txnSet, nil
   704  }
   705  
   706  // ResetContractUtilities will reset all bad and locked contracts, as well
   707  // as trigger a contract maintenance.
   708  func (c *Contractor) ResetContractUtilities() error {
   709  	// add ourselves to the threadgroup
   710  	err := c.staticTG.Add()
   711  	if err != nil {
   712  		return err
   713  	}
   714  	defer c.staticTG.Done()
   715  
   716  	// reset contract utilities
   717  	err = c.managedResetContractUtilities()
   718  	if err != nil {
   719  		return err
   720  	}
   721  
   722  	// schedule maintenance
   723  	go c.threadedContractMaintenance()
   724  
   725  	return nil
   726  }
   727  
   728  // managedResetContractUtilities will reset all bad and locked contracts, as well
   729  // as trigger a contract maintenance.
   730  func (c *Contractor) managedResetContractUtilities() error {
   731  	// reset the failed renews map
   732  	c.managedResetFailedRenews()
   733  
   734  	// reset all cancelled contracts
   735  	var errs []error
   736  	for _, rc := range c.staticContracts.ViewAll() {
   737  		if rc.Utility.BadContract || rc.Utility.Locked {
   738  			errs = append(errs, errors.AddContext(c.managedResetContract(rc.ID), fmt.Sprintf("failed to reset contract %v", rc.ID)))
   739  		}
   740  	}
   741  	return errors.Compose(errs...)
   742  }
   743  
   744  // managedResetFailedRenews resets the failed renews map on the contractor
   745  func (c *Contractor) managedResetFailedRenews() {
   746  	c.mu.Lock()
   747  	defer c.mu.Unlock()
   748  	c.numFailedRenews = make(map[types.FileContractID]types.BlockHeight)
   749  }