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

     1  package contractor
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"gitlab.com/SkynetLabs/skyd/build"
     7  	"gitlab.com/SkynetLabs/skyd/skymodules"
     8  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/proto"
     9  	"go.sia.tech/siad/types"
    10  
    11  	"gitlab.com/NebulousLabs/errors"
    12  )
    13  
    14  // contractEndHeight returns the height at which the Contractor's contracts
    15  // end.
    16  func (c *Contractor) contractEndHeight() types.BlockHeight {
    17  	return c.currentPeriod + c.allowance.Period + c.allowance.RenewWindow
    18  }
    19  
    20  // managedCancelContract cancels a contract by setting its utility fields to
    21  // false and locking the utilities. The contract can still be used for downloads
    22  // after this but it won't be used for uploads or renewals. Canceling a contract
    23  // doesn't count towards host churn.
    24  func (c *Contractor) managedCancelContract(cid types.FileContractID) error {
    25  	return c.managedAcquireAndUpdateContractUtility(cid, skymodules.ContractUtility{
    26  		GoodForRefresh: false,
    27  		GoodForRenew:   false,
    28  		GoodForUpload:  false,
    29  		Locked:         true,
    30  	}, true)
    31  }
    32  
    33  // managedContractByPublicKey returns the contract with the key specified, if
    34  // it exists. The contract will be resolved if possible to the most recent
    35  // child contract.
    36  func (c *Contractor) managedContractByPublicKey(pk types.SiaPublicKey) (skymodules.RenterContract, bool) {
    37  	c.mu.RLock()
    38  	id, ok := c.pubKeysToContractID[pk.String()]
    39  	c.mu.RUnlock()
    40  	if !ok {
    41  		return skymodules.RenterContract{}, false
    42  	}
    43  	return c.staticContracts.View(id)
    44  }
    45  
    46  // managedContractUtility returns the ContractUtility for a contract with a given id.
    47  func (c *Contractor) managedContractUtility(id types.FileContractID) (skymodules.ContractUtility, bool) {
    48  	rc, exists := c.staticContracts.View(id)
    49  	if !exists {
    50  		return skymodules.ContractUtility{}, false
    51  	}
    52  	return rc.Utility, true
    53  }
    54  
    55  // managedResetContract acquires the contract with given id and resets it by
    56  // clearing its locked and bad fields.
    57  func (c *Contractor) managedResetContract(id types.FileContractID) error {
    58  	sc, exists := c.staticContracts.Acquire(id)
    59  	if !exists {
    60  		return errors.New("contract not found")
    61  	}
    62  	defer c.staticContracts.Return(sc)
    63  
    64  	return c.callUpdateUtility(sc, skymodules.ContractUtility{
    65  		Locked:      false,
    66  		BadContract: false,
    67  	}, false)
    68  }
    69  
    70  // managedUpdatePubKeyToContractIDMap updates the pubkeysToContractID map
    71  func (c *Contractor) managedUpdatePubKeyToContractIDMap() {
    72  	// Grab the current contracts and the blockheight
    73  	contracts := c.staticContracts.ViewAll()
    74  	c.mu.Lock()
    75  	c.updatePubKeyToContractIDMap(contracts)
    76  	c.mu.Unlock()
    77  }
    78  
    79  // updatePubKeyToContractIDMap updates the pubkeysToContractID map
    80  func (c *Contractor) updatePubKeyToContractIDMap(contracts []skymodules.RenterContract) {
    81  	// Sanity check - there should be an equal number of GFU contracts in each
    82  	// the ViewAll set of contracts, and also in the pubKeyToContractID map.
    83  	uniqueGFU := make(map[string]types.FileContractID)
    84  
    85  	// Reset the pubkey to contract id map, also create a map from each
    86  	// contract's fcid to the contract itself, then try adding each contract to
    87  	// the map. The most recent contract for each host will be favored as the
    88  	// contract in the map.
    89  	c.pubKeysToContractID = make(map[string]types.FileContractID)
    90  	for i := 0; i < len(contracts); i++ {
    91  		c.tryAddContractToPubKeyMap(contracts[i])
    92  
    93  		// Fill out the uniqueGFU map, tracking every contract that is marked as
    94  		// GoodForUpload.
    95  		if contracts[i].Utility.GoodForUpload {
    96  			uniqueGFU[contracts[i].HostPublicKey.String()] = contracts[i].ID
    97  		}
    98  	}
    99  
   100  	// Every contract that appears in the uniqueGFU map should also appear in
   101  	// the pubKeysToContractID map.
   102  	for pk, fcid := range uniqueGFU {
   103  		if c.pubKeysToContractID[pk] != fcid {
   104  			build.Critical("Contractor is not correctly mapping from pubkey to contract id, missing GFU contracts")
   105  		}
   106  	}
   107  }
   108  
   109  // tryAddContractToPubKeyMap will try and add the contract to the
   110  // pubKeysToContractID map. The most recent contract with the best utility for
   111  // each pubKey will be added
   112  func (c *Contractor) tryAddContractToPubKeyMap(newContract skymodules.RenterContract) {
   113  	// Ignore any contracts that have been renewed.
   114  	_, exists := c.renewedTo[newContract.ID]
   115  	if exists {
   116  		gfu, gfr := newContract.Utility.GoodForUpload, newContract.Utility.GoodForRenew
   117  		if gfu || gfr {
   118  			c.staticLog.Critical("renewed contract is marked as good for upload or good for renew", gfu, gfr)
   119  		}
   120  		return
   121  	}
   122  	pk := newContract.HostPublicKey.String()
   123  
   124  	// If there is not existing contract in the map for this pubkey, add it.
   125  	_, exists = c.pubKeysToContractID[pk]
   126  	if exists {
   127  		// Sanity check - the contractor should not have multiple contract tips for the
   128  		// same contract.
   129  		c.staticLog.Critical("Contractor has multiple contracts that don't form a renewedTo line for the same host")
   130  	}
   131  	c.pubKeysToContractID[pk] = newContract.ID
   132  }
   133  
   134  // ContractByPublicKey returns the contract with the key specified, if it
   135  // exists. The contract will be resolved if possible to the most recent child
   136  // contract.
   137  func (c *Contractor) ContractByPublicKey(pk types.SiaPublicKey) (skymodules.RenterContract, bool) {
   138  	return c.managedContractByPublicKey(pk)
   139  }
   140  
   141  // CancelContract cancels the Contractor's contract by marking it !GoodForRenew
   142  // and !GoodForUpload
   143  func (c *Contractor) CancelContract(id types.FileContractID) error {
   144  	if err := c.staticTG.Add(); err != nil {
   145  		return err
   146  	}
   147  	defer c.staticTG.Done()
   148  	defer c.threadedContractMaintenance()
   149  	return c.managedCancelContract(id)
   150  }
   151  
   152  // Contracts returns the contracts formed by the contractor in the current
   153  // allowance period. Only contracts formed with currently online hosts are
   154  // returned.
   155  func (c *Contractor) Contracts() []skymodules.RenterContract {
   156  	return c.staticContracts.ViewAll()
   157  }
   158  
   159  // ContractUtility returns the utility fields for the given contract.
   160  func (c *Contractor) ContractUtility(pk types.SiaPublicKey) (skymodules.ContractUtility, bool) {
   161  	c.mu.RLock()
   162  	id, ok := c.pubKeysToContractID[pk.String()]
   163  	c.mu.RUnlock()
   164  	if !ok {
   165  		return skymodules.ContractUtility{}, false
   166  	}
   167  	return c.managedContractUtility(id)
   168  }
   169  
   170  // MarkContractBad will mark a specific contract as bad.
   171  func (c *Contractor) MarkContractBad(id types.FileContractID) error {
   172  	if err := c.staticTG.Add(); err != nil {
   173  		return err
   174  	}
   175  	defer c.staticTG.Done()
   176  
   177  	sc, exists := c.staticContracts.Acquire(id)
   178  	if !exists {
   179  		return errors.New("contract not found")
   180  	}
   181  	defer c.staticContracts.Return(sc)
   182  	return c.managedMarkContractBad(sc)
   183  }
   184  
   185  // OldContracts returns the contracts formed by the contractor that have
   186  // expired
   187  func (c *Contractor) OldContracts() []skymodules.RenterContract {
   188  	c.mu.Lock()
   189  	defer c.mu.Unlock()
   190  	contracts := make([]skymodules.RenterContract, 0, len(c.oldContracts))
   191  	for _, c := range c.oldContracts {
   192  		contracts = append(contracts, c)
   193  	}
   194  	return contracts
   195  }
   196  
   197  // RecoverableContracts returns the contracts that the contractor deems
   198  // recoverable. That means they are not expired yet and also not part of the
   199  // active contracts. Usually this should return an empty slice unless the host
   200  // isn't available for recovery or something went wrong.
   201  func (c *Contractor) RecoverableContracts() []skymodules.RecoverableContract {
   202  	c.mu.Lock()
   203  	defer c.mu.Unlock()
   204  	contracts := make([]skymodules.RecoverableContract, 0, len(c.recoverableContracts))
   205  	for _, c := range c.recoverableContracts {
   206  		contracts = append(contracts, c)
   207  	}
   208  	return contracts
   209  }
   210  
   211  // managedMarkContractBad marks an already acquired SafeContract as bad.
   212  func (c *Contractor) managedMarkContractBad(sc *proto.SafeContract) error {
   213  	u := sc.Utility()
   214  	msg := fmt.Sprintf("[CONTRACTUTILITY][%v] marking contract as bad, bad: %v -> true, GFU: %v -> false, GFR: %v -> false, GFRef: %v -> false", sc.Metadata().ID, u.BadContract, u.GoodForUpload, u.GoodForRenew, u.GoodForRefresh)
   215  	u.GoodForUpload = false
   216  	u.GoodForRefresh = false
   217  	u.GoodForRenew = false
   218  	u.BadContract = true
   219  	err := c.callUpdateUtility(sc, u, false)
   220  	if err != nil {
   221  		return errors.AddContext(err, "unable to mark contract as bad")
   222  	}
   223  	c.staticLog.Println(msg)
   224  	return nil
   225  }