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 }