gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/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" 10 "math/big" 11 "reflect" 12 "time" 13 14 "gitlab.com/NebulousLabs/errors" 15 "gitlab.com/NebulousLabs/fastrand" 16 17 "gitlab.com/SkynetLabs/skyd/build" 18 "gitlab.com/SkynetLabs/skyd/skymodules" 19 "gitlab.com/SkynetLabs/skyd/skymodules/renter/proto" 20 "go.sia.tech/siad/modules" 21 "go.sia.tech/siad/persist" 22 "go.sia.tech/siad/types" 23 ) 24 25 const ( 26 // MaxCriticalRenewFailThreshold is the maximum number of contracts failing 27 // to renew as fraction of the total gfr contracts in the allowance before 28 // renew alerts are made critical. 29 MaxCriticalRenewFailThreshold = 0.2 30 31 // renewWindowLeewayDivisor controls how early we want to raise a renew 32 // error from severe to critical. Taking a divisor of 4, means that we raise 33 // a critical if a certain amount of renewals failed and if one fourth of 34 // the renewal window has passed. This seems like a sane value as we delete 35 // a contract after half the window has passed and the renew failed 12 36 // consecutive times, so it should give the error some time to resolve 37 // itself before becoming a critical. 38 renewWindowLeewayDivisor = 4 39 ) 40 41 var ( 42 // ErrInsufficientAllowance indicates that the renter's allowance is less 43 // than the amount necessary to store at least one sector 44 ErrInsufficientAllowance = errors.New("allowance is not large enough to cover fees of contract creation") 45 errTooExpensive = errors.New("host price was too high") 46 47 // errContractEnded is the error returned when the contract has already ended 48 errContractEnded = errors.New("contract has already ended") 49 50 // errContractNotGFR is used to indicate that a contract renewal failed 51 // because the contract was marked !GFR. 52 errContractNotGFR = errors.New("contract is not GoodForRenew") 53 54 // errHostBlocked is the error returned when the host is blocked 55 errHostBlocked = errors.New("host is blocked") 56 ) 57 58 type ( 59 // decoratedFileContractRenewal wraps a fileContractRenewal struct and 60 // decorates it with a boolean that indicates whether the contract renewal 61 // is already a certain amount of time into the renew window 62 decoratedFileContractRenewal struct { 63 fileContractRenewal 64 65 lateRenewal bool 66 } 67 68 // fileContractRenewal is an instruction to renew a file contract. 69 fileContractRenewal struct { 70 id types.FileContractID 71 amount types.Currency 72 data uint64 73 hostPubKey types.SiaPublicKey 74 } 75 ) 76 77 // hostsForPortalFormation returns the hosts to form contracts with for a 78 // portal. In contrast to regular renters, portals form contracts with every 79 // host. It only ignores hosts that fail the gouging, have a bad score or hosts 80 // that we have recoverable contracts with. 81 func hostsForPortalFormation(allowance skymodules.Allowance, allContracts []skymodules.RenterContract, recoverableContracts []skymodules.RecoverableContract, activeHosts []skymodules.HostDBEntry, l *persist.Logger, scoreBreakdown func(skymodules.HostDBEntry) (skymodules.HostScoreBreakdown, error)) (int, []skymodules.HostDBEntry) { 82 if !allowance.PortalMode() { 83 build.Critical("hostsForPortalFormation was called on a non-portal") 84 return 0, nil 85 } 86 // Get a list of all current contracts. 87 currentContracts := make(map[string]skymodules.RenterContract) 88 for _, contract := range allContracts { 89 currentContracts[contract.HostPublicKey.String()] = contract 90 } 91 // Get a map of hosts we have recoverable contracts with. 92 recoverable := make(map[string]struct{}) 93 for _, contract := range recoverableContracts { 94 recoverable[contract.HostPublicKey.String()] = struct{}{} 95 } 96 97 var hosts []skymodules.HostDBEntry 98 for _, host := range activeHosts { 99 // Check if there is already a contract with this host. 100 _, exists := currentContracts[host.PublicKey.String()] 101 if exists { 102 continue 103 } 104 105 // Skip host if it has a dead score. 106 sb, err := scoreBreakdown(host) 107 if err != nil || sb.Score.Cmp(types.NewCurrency64(1)) <= 0 { 108 l.Debugf("skipping host %v due to dead or unknown score (%v)", host.PublicKey, err) 109 continue 110 } 111 112 // Skip host if we have a recoverable contract with it. 113 _, recoverableContract := recoverable[host.PublicKey.String()] 114 if recoverableContract { 115 l.Debugf("skipping host %v due to having a recoverable contract with it", host.PublicKey) 116 continue 117 } 118 119 // Check that the price settings of the host are acceptable. 120 hostSettings := host.HostExternalSettings 121 err = staticCheckFormPaymentContractGouging(allowance, hostSettings) 122 if err != nil { 123 l.Debugf("payment contract loop igorning host %v for gouging: %v", hostSettings, err) 124 continue 125 } 126 127 // Append host if it passed all checks. 128 hosts = append(hosts, host) 129 } 130 return len(hosts), hosts 131 } 132 133 // hostsForRegularFormation returns the number of hosts needed for 134 // non-portal contract formation plus a set of hosts to use. 135 func hostsForRegularFormation(allowance skymodules.Allowance, allContracts []skymodules.RenterContract, recoverableContracts []skymodules.RecoverableContract, randomHosts func(_ int, _, _ []types.SiaPublicKey) ([]skymodules.HostDBEntry, error), l *persist.Logger) (int, []skymodules.HostDBEntry) { 136 if allowance.PortalMode() { 137 build.Critical("hostsForRegularFormation was called on a portal") 138 return 0, nil 139 } 140 // Count the number of contracts which are good for uploading, and then make 141 // more as needed to fill the gap. 142 uploadContracts := 0 143 for _, c := range allContracts { 144 if c.Utility.GoodForUpload { 145 uploadContracts++ 146 } 147 } 148 neededContracts := int(allowance.Hosts) - uploadContracts 149 if neededContracts <= 0 { 150 l.Debugln("do not seem to need more contracts") 151 return 0, nil 152 } 153 if neededContracts > 0 { 154 l.Println("need more contracts:", neededContracts) 155 } 156 157 // Assemble two exclusion lists. The first one excludes all hosts that we 158 // already have contracts with and the second one excludes all hosts we have 159 // active contracts with. Then select a new batch of hosts to attempt 160 // contract formation with. 161 var blacklist []types.SiaPublicKey 162 var addressBlacklist []types.SiaPublicKey 163 for _, contract := range allContracts { 164 blacklist = append(blacklist, contract.HostPublicKey) 165 if !contract.Utility.Locked || contract.Utility.GoodForRenew || contract.Utility.GoodForUpload { 166 addressBlacklist = append(addressBlacklist, contract.HostPublicKey) 167 } 168 } 169 // Add the hosts we have recoverable contracts with to the blacklist to 170 // avoid losing existing data by forming a new/empty contract. 171 for _, contract := range recoverableContracts { 172 blacklist = append(blacklist, contract.HostPublicKey) 173 } 174 175 hosts, err := randomHosts(neededContracts*4+randomHostsBufferForScore, blacklist, addressBlacklist) 176 if err != nil { 177 l.Println("WARN: not forming new contracts:", err) 178 return 0, nil 179 } 180 l.Debugln("trying to form contracts with hosts, pulled this many hosts from hostdb:", len(hosts)) 181 return neededContracts, hosts 182 } 183 184 // initialContractFunding computes the amount of money to put into the first 185 // contract formed with a host. 186 func initialContractFunding(a skymodules.Allowance, host skymodules.HostDBEntry, txnFee, min, max types.Currency) types.Currency { 187 if a.PortalMode() { 188 return a.PaymentContractInitialFunding 189 } 190 191 // Calculate the contract funding with host 192 contractFunds := host.ContractPrice.Add(txnFee).Mul64(ContractFeeFundingMulFactor) 193 194 // Check that the contract funding is reasonable compared to the max and 195 // min initial funding. This is to protect against increases to 196 // allowances being used up to fast and not being able to spread the 197 // funds across new contracts properly, as well as protecting against 198 // contracts renewing too quickly 199 if min.Cmp(max) > 0 && !max.IsZero() { 200 build.Critical(fmt.Sprintf("WARN: initialContractFunding min > max (%v > %v)", min, max)) 201 } 202 if contractFunds.Cmp(max) > 0 && !max.IsZero() { 203 return max 204 } 205 if contractFunds.Cmp(min) < 0 { 206 return min 207 } 208 return contractFunds 209 } 210 211 // callNotifyDoubleSpend is used by the watchdog to alert the contractor 212 // whenever a monitored file contract input is double-spent. This function 213 // marks down the host score, and marks the contract as !GoodForRenew and 214 // !GoodForUpload. 215 func (c *Contractor) callNotifyDoubleSpend(fcID types.FileContractID, blockHeight types.BlockHeight) { 216 c.staticLog.Println("Watchdog found a double-spend: ", fcID, blockHeight) 217 218 // Mark the contract as double-spent. This will cause the contract to be 219 // excluded in period spending. 220 c.mu.Lock() 221 c.doubleSpentContracts[fcID] = blockHeight 222 c.mu.Unlock() 223 224 err := c.MarkContractBad(fcID) 225 if err != nil { 226 c.staticLog.Println("callNotifyDoubleSpend error in MarkContractBad", err) 227 } 228 } 229 230 // managedCheckForDuplicates checks for static contracts that have the same host 231 // key and moves the older one to old contracts. 232 func (c *Contractor) managedCheckForDuplicates() { 233 // Build map for comparison. 234 pubkeys := make(map[string]types.FileContractID) 235 var newContract, oldContract skymodules.RenterContract 236 for _, contract := range c.staticContracts.ViewAll() { 237 id, exists := pubkeys[contract.HostPublicKey.String()] 238 if !exists { 239 pubkeys[contract.HostPublicKey.String()] = contract.ID 240 continue 241 } 242 243 // Duplicate contract found, determine older contract to delete. 244 if rc, ok := c.staticContracts.View(id); ok { 245 if rc.StartHeight >= contract.StartHeight { 246 newContract, oldContract = rc, contract 247 } else { 248 newContract, oldContract = contract, rc 249 } 250 c.staticLog.Printf("Duplicate contract found. New contract is %x and old contract is %v", newContract.ID, oldContract.ID) 251 252 // Get SafeContract 253 oldSC, ok := c.staticContracts.Acquire(oldContract.ID) 254 if !ok { 255 // Update map 256 pubkeys[contract.HostPublicKey.String()] = newContract.ID 257 continue 258 } 259 260 // Link the contracts to each other and then store the old contract 261 // in the record of historic contracts. 262 // 263 // Note: This means that if there are multiple duplicates, say 3 264 // contracts that all share the same host, then the ordering may not 265 // be perfect. If in reality the renewal order was A<->B<->C, it's 266 // possible for the contractor to end up with A->C and B<->C in the 267 // mapping. 268 c.mu.Lock() 269 c.renewedFrom[newContract.ID] = oldContract.ID 270 c.renewedTo[oldContract.ID] = newContract.ID 271 c.oldContracts[oldContract.ID] = oldSC.Metadata() 272 273 // Save the contractor and delete the contract. 274 // 275 // TODO: Ideally these two things would happen atomically, but I'm 276 // not completely certain that's feasible with our current 277 // architecture. 278 // 279 // TODO: This should revert the in memory state in the event of an 280 // error and continue 281 err := c.save() 282 if err != nil { 283 c.staticLog.Println("Failed to save the contractor after updating renewed maps.") 284 } 285 c.mu.Unlock() 286 c.staticContracts.Delete(oldSC) 287 288 // Update the pubkeys map to contain the newest contract id. 289 pubkeys[contract.HostPublicKey.String()] = newContract.ID 290 } 291 } 292 } 293 294 // managedEstimateRenewFundingRequirements estimates the amount of money that a 295 // contract is going to need in the next billing cycle by looking at how much 296 // storage is in the contract and what the historic usage pattern of the 297 // contract has been. 298 func (c *Contractor) managedEstimateRenewFundingRequirements(contract skymodules.RenterContract, gfrContracts uint64, blockHeight types.BlockHeight, allowance skymodules.Allowance) (types.Currency, error) { 299 // Fetch the host pricing to use in the estimate. 300 host, exists, err := c.staticHDB.Host(contract.HostPublicKey) 301 if err != nil { 302 return types.ZeroCurrency, errors.AddContext(err, "error getting host from hostdb:") 303 } 304 if !exists { 305 return types.ZeroCurrency, errors.New("could not find host in hostdb") 306 } 307 if host.Filtered { 308 return types.ZeroCurrency, errHostBlocked 309 } 310 311 // Estimate the amount of money that's going to be needed for existing 312 // storage. 313 dataStored := contract.Transaction.FileContractRevisions[0].NewFileSize 314 storageCost := types.NewCurrency64(dataStored).Mul64(uint64(allowance.Period)).Mul(host.StoragePrice) 315 316 // For the spending estimates, we're going to need to know the amount of 317 // money that was spent on upload and download by this contract line in this 318 // period. That's going to require iterating over the renew history of the 319 // contract to get all the spending across any refreshes that occurred this 320 // period. 321 prevUploadSpending := contract.UploadSpending 322 prevDownloadSpending := contract.DownloadSpending 323 prevFundAccountSpending := contract.FundAccountSpending 324 prevMaintenanceSpending := contract.MaintenanceSpending 325 c.mu.Lock() 326 currentID := contract.ID 327 for i := 0; i < 10e3; i++ { // prevent an infinite loop if there's an [impossible] contract cycle 328 // If there is no previous contract, nothing to do. 329 var exists bool 330 currentID, exists = c.renewedFrom[currentID] 331 if !exists { 332 break 333 } 334 335 // If the contract is not in oldContracts, that's probably a bug, but 336 // nothing to do otherwise. 337 currentContract, exists := c.oldContracts[currentID] 338 if !exists { 339 c.staticLog.Println("WARN: A known previous contract is not found in c.oldContracts") 340 break 341 } 342 343 // If the contract did not start in the current period, then it is not 344 // relevant, and none of the previous contracts will be relevant either. 345 if currentContract.StartHeight < c.currentPeriod { 346 break 347 } 348 349 // Add the historical spending metrics. 350 prevUploadSpending = prevUploadSpending.Add(currentContract.UploadSpending) 351 prevDownloadSpending = prevDownloadSpending.Add(currentContract.DownloadSpending) 352 prevFundAccountSpending = prevFundAccountSpending.Add(currentContract.FundAccountSpending) 353 prevMaintenanceSpending = prevMaintenanceSpending.Add(currentContract.MaintenanceSpending) 354 } 355 c.mu.Unlock() 356 357 // Estimate the amount of money that's going to be needed for new storage 358 // based on the amount of new storage added in the previous period. Account 359 // for both the storage price as well as the upload price. 360 prevUploadDataEstimate := prevUploadSpending 361 if !host.UploadBandwidthPrice.IsZero() { 362 // TODO: Because the host upload bandwidth price can change, this is not 363 // the best way to estimate the amount of data that was uploaded to this 364 // contract. Better would be to look at the amount of data stored in the 365 // contract from the previous cycle and use that to determine how much 366 // total data. 367 prevUploadDataEstimate = prevUploadDataEstimate.Div(host.UploadBandwidthPrice) 368 } 369 // Sanity check - the host may have changed prices, make sure we aren't 370 // assuming an unreasonable amount of data. 371 if types.NewCurrency64(dataStored).Cmp(prevUploadDataEstimate) < 0 { 372 prevUploadDataEstimate = types.NewCurrency64(dataStored) 373 } 374 // The estimated cost for new upload spending is the previous upload 375 // bandwidth plus the implied storage cost for all of the new data. 376 newUploadsCost := prevUploadSpending.Add(prevUploadDataEstimate.Mul64(uint64(allowance.Period)).Mul(host.StoragePrice)) 377 378 // The download cost is assumed to be the same. Even if the user is 379 // uploading more data, the expectation is that the download amounts will be 380 // relatively constant. Add in the contract price as well. 381 newDownloadsCost := prevDownloadSpending 382 383 // The estimated cost for funding ephemeral accounts and performing RHP3 384 // maintenance such as updating price tables and syncing the ephemeral 385 // account balance is expected to remain identical. 386 newFundAccountCost := prevFundAccountSpending 387 newMaintenanceCost := prevMaintenanceSpending.Sum() 388 389 contractPrice := host.ContractPrice 390 391 // Aggregate all estimates so far to compute the estimated siafunds fees. 392 // The transaction fees are not included in the siafunds estimate because 393 // users are not charged siafund fees on money that doesn't go into the file 394 // contract (and the transaction fee goes to the miners, not the file 395 // contract). 396 beforeSiafundFeesEstimate := storageCost.Add(newUploadsCost).Add(newDownloadsCost).Add(newFundAccountCost).Add(newMaintenanceCost).Add(contractPrice) 397 afterSiafundFeesEstimate := types.Tax(blockHeight, beforeSiafundFeesEstimate).Add(beforeSiafundFeesEstimate) 398 399 // Get an estimate for how much money we will be charged before going into 400 // the transaction pool. 401 _, maxTxnFee := c.staticTPool.FeeEstimation() 402 txnFees := maxTxnFee.Mul64(skymodules.EstimatedFileContractTransactionSetSize) 403 404 // Add them all up and then return the estimate plus 33% for error margin 405 // and just general volatility of usage pattern. 406 estimatedCost := afterSiafundFeesEstimate.Add(txnFees) 407 estimatedCost = estimatedCost.Add(estimatedCost.Div64(3)) 408 409 // Check for a sane minimum that is equal to the initial contract funding 410 // but without an upper cap. 411 minInitialContractFunds := allowance.Funds.Div64(allowance.Hosts).Div64(MinInitialContractFundingDivFactor) 412 minimum := initialContractFunding(allowance, host, txnFees, minInitialContractFunds, types.ZeroCurrency) 413 if estimatedCost.Cmp(minimum) < 0 { 414 c.staticLog.Printf("Contract renew amount %v below minimum amount %v", estimatedCost, minimum) 415 estimatedCost = minimum 416 } 417 return estimatedCost, nil 418 } 419 420 // callInterruptContractMaintenance will issue an interrupt signal to any 421 // running maintenance, stopping that maintenance. If there are multiple threads 422 // running maintenance, they will all be stopped. 423 func (c *Contractor) callInterruptContractMaintenance() { 424 // Spin up a thread to grab the maintenance lock. Signal that the lock was 425 // acquired after the lock is acquired. 426 gotLock := make(chan struct{}) 427 go func() { 428 c.maintenanceLock.Lock() 429 close(gotLock) 430 c.maintenanceLock.Unlock() 431 }() 432 433 // There may be multiple threads contending for the maintenance lock. Issue 434 // interrupts repeatedly until we get a signal that the maintenance lock has 435 // been acquired. 436 for { 437 select { 438 case <-gotLock: 439 return 440 case c.staticInterruptMaintenance <- struct{}{}: 441 c.staticLog.Debugln("Signal sent to interrupt contract maintenance") 442 } 443 } 444 } 445 446 // managedAddPreferredHosts adds toAdd hosts to the preferredHosts set from the 447 // potentialHosts set and removes them from the potentialHosts. 448 func (c *Contractor) managedAddPreferredHosts(toAdd int, preferredHosts, potentialHosts map[string]struct{}) { 449 // Grab random hosts from the potential set that are not already in the 450 // preferred set. To do so, we use the preferred hosts as the blacklist and 451 // the potential hosts as the whitelist. 452 var blacklist []types.SiaPublicKey 453 for host := range preferredHosts { 454 var spk types.SiaPublicKey 455 err := spk.LoadString(host) 456 if err != nil { 457 build.Critical("managedLimitGFUHosts: failed to marshal hostkey", err) 458 delete(preferredHosts, host) 459 continue 460 } 461 // Ignore hosts that are already in the preferred set. 462 blacklist = append(blacklist, spk) 463 } 464 hosts, err := c.staticHDB.RandomHostsWithWhitelist(toAdd, blacklist, blacklist, potentialHosts) 465 if err != nil { 466 c.staticLog.Print("managedLimitGFUHosts: failed to get random hosts:", err) 467 return 468 } 469 470 // Add hosts to fill the set. 471 for i := 0; i < len(hosts) && toAdd > 0; i++ { 472 host := hosts[i].PublicKey.String() 473 _, exists := potentialHosts[host] 474 if !exists { 475 continue // try next 476 } 477 preferredHosts[host] = struct{}{} 478 delete(potentialHosts, host) 479 toAdd-- 480 } 481 } 482 483 // managedFindMinAllowedHostScores uses a set of random hosts from the hostdb to 484 // calculate minimum acceptable score for a host to be marked GFR and GFU. 485 func (c *Contractor) managedFindMinAllowedHostScores(hostCount uint64) (types.Currency, types.Currency, error) { 486 // Pull a new set of hosts from the hostdb that could be used as a new set 487 // to match the allowance. The lowest scoring host of these new hosts will 488 // be used as a baseline for determining whether our existing contracts are 489 // worthwhile. 490 hosts, err := c.staticHDB.RandomHosts(int(hostCount)+randomHostsBufferForScore, nil, nil) 491 if err != nil { 492 return types.Currency{}, types.Currency{}, err 493 } 494 495 if len(hosts) == 0 { 496 return types.Currency{}, types.Currency{}, errors.New("No hosts returned in RandomHosts") 497 } 498 499 // Find the minimum score that a host is allowed to have to be considered 500 // good for upload. 501 var minScoreGFR, minScoreGFU types.Currency 502 sb, err := c.staticHDB.ScoreBreakdown(hosts[0]) 503 if err != nil { 504 return types.Currency{}, types.Currency{}, err 505 } 506 507 lowestScore := sb.Score 508 for i := 1; i < len(hosts); i++ { 509 score, err := c.staticHDB.ScoreBreakdown(hosts[i]) 510 if err != nil { 511 return types.Currency{}, types.Currency{}, err 512 } 513 if score.Score.Cmp(lowestScore) < 0 { 514 lowestScore = score.Score 515 } 516 } 517 // Set the minimum acceptable score to a factor of the lowest score. 518 minScoreGFR = lowestScore.Div(scoreLeewayGoodForRenew) 519 minScoreGFU = lowestScore.Div(scoreLeewayGoodForUpload) 520 521 // Set min score to the max score seen times 2. 522 if c.staticDeps.Disrupt("HighMinHostScore") { 523 var maxScore types.Currency 524 for i := 1; i < len(hosts); i++ { 525 score, err := c.staticHDB.ScoreBreakdown(hosts[i]) 526 if err != nil { 527 return types.Currency{}, types.Currency{}, err 528 } 529 if score.Score.Cmp(maxScore) > 0 { 530 maxScore = score.Score 531 } 532 } 533 minScoreGFR = maxScore.Mul64(2) 534 } 535 536 return minScoreGFR, minScoreGFU, nil 537 } 538 539 // managedNewContract negotiates an initial file contract with the specified 540 // host, saves it, and returns it. 541 func (c *Contractor) managedNewContract(host skymodules.HostDBEntry, allowance skymodules.Allowance, contractFunding types.Currency, endHeight types.BlockHeight) (_ types.Currency, _ skymodules.RenterContract, err error) { 542 // reject hosts that are too expensive 543 if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 544 return types.ZeroCurrency, skymodules.RenterContract{}, errTooExpensive 545 } 546 // Determine if host settings align with allowance period 547 c.mu.Lock() 548 if reflect.DeepEqual(allowance, skymodules.Allowance{}) { 549 c.mu.Unlock() 550 return types.ZeroCurrency, skymodules.RenterContract{}, errors.New("called managedNewContract but allowance wasn't set") 551 } 552 hostSettings := host.HostExternalSettings 553 period := allowance.Period 554 c.mu.Unlock() 555 556 if host.MaxDuration < period { 557 err := errors.New("unable to form contract with host due to insufficient MaxDuration of host") 558 return types.ZeroCurrency, skymodules.RenterContract{}, err 559 } 560 // cap host.MaxCollateral 561 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 562 host.MaxCollateral = maxCollateral 563 } 564 565 // Check for price gouging. 566 err = checkFormContractGouging(allowance, hostSettings) 567 if err != nil { 568 return types.ZeroCurrency, skymodules.RenterContract{}, errors.AddContext(err, "unable to form a contract due to price gouging detection") 569 } 570 571 // get an address to use for negotiation 572 uc, err := c.staticWallet.NextAddress() 573 if err != nil { 574 return types.ZeroCurrency, skymodules.RenterContract{}, err 575 } 576 defer func() { 577 if err != nil { 578 err = errors.Compose(err, c.staticWallet.MarkAddressUnused(uc)) 579 } 580 }() 581 582 // get the wallet seed. 583 seed, _, err := c.staticWallet.PrimarySeed() 584 if err != nil { 585 return types.ZeroCurrency, skymodules.RenterContract{}, err 586 } 587 // derive the renter seed and wipe it once we are done with it. 588 renterSeed := skymodules.DeriveRenterSeed(seed) 589 defer fastrand.Read(renterSeed[:]) 590 591 // create contract params 592 c.mu.RLock() 593 params := skymodules.ContractParams{ 594 Allowance: allowance, 595 Host: host, 596 Funding: contractFunding, 597 StartHeight: c.blockHeight, 598 EndHeight: endHeight, 599 RefundAddress: uc.UnlockHash(), 600 RenterSeed: renterSeed.EphemeralRenterSeed(endHeight), 601 } 602 c.mu.RUnlock() 603 604 // wipe the renter seed once we are done using it. 605 defer fastrand.Read(params.RenterSeed[:]) 606 607 // create transaction builder and trigger contract formation. 608 txnBuilder, err := c.staticWallet.StartTransaction() 609 if err != nil { 610 return types.ZeroCurrency, skymodules.RenterContract{}, err 611 } 612 613 contract, formationTxnSet, sweepTxn, sweepParents, err := c.staticContracts.FormContract(params, txnBuilder, c.staticTPool, c.staticHDB, c.staticTG.StopChan()) 614 if err != nil { 615 txnBuilder.Drop() 616 return types.ZeroCurrency, skymodules.RenterContract{}, err 617 } 618 619 monitorContractArgs := monitorContractArgs{ 620 false, 621 contract.ID, 622 contract.Transaction, 623 formationTxnSet, 624 sweepTxn, 625 sweepParents, 626 params.StartHeight, 627 } 628 err = c.staticWatchdog.callMonitorContract(monitorContractArgs) 629 if err != nil { 630 return types.ZeroCurrency, skymodules.RenterContract{}, err 631 } 632 633 // Add a mapping from the contract's id to the public key of the host. 634 c.mu.Lock() 635 _, exists := c.pubKeysToContractID[contract.HostPublicKey.String()] 636 if exists { 637 c.mu.Unlock() 638 txnBuilder.Drop() 639 // We need to return a funding value because money was spent on this 640 // host, even though the full process could not be completed. 641 c.staticLog.Println("WARN: Attempted to form a new contract with a host that we already have a contrat with.") 642 return contractFunding, skymodules.RenterContract{}, fmt.Errorf("We already have a contract with host %v", contract.HostPublicKey) 643 } 644 c.pubKeysToContractID[contract.HostPublicKey.String()] = contract.ID 645 c.mu.Unlock() 646 647 contractValue := contract.RenterFunds 648 c.staticLog.Printf("Formed contract %v with %v for %v", contract.ID, host.NetAddress, contractValue.HumanString()) 649 650 // Update the hostdb to include the new contract. 651 err = c.staticHDB.UpdateContracts(c.staticContracts.ViewAll()) 652 if err != nil { 653 c.staticLog.Println("Unable to update hostdb contracts:", err) 654 } 655 return contractFunding, contract, nil 656 } 657 658 // managedPrunedRedundantAddressRange uses the hostdb to find hosts that 659 // violate the rules about address ranges and cancels them. 660 func (c *Contractor) managedPrunedRedundantAddressRange() { 661 // Get all contracts which are not canceled. 662 allContracts := c.staticContracts.ViewAll() 663 var contracts []skymodules.RenterContract 664 for _, contract := range allContracts { 665 if contract.Utility.Locked && !contract.Utility.GoodForRenew && !contract.Utility.GoodForUpload { 666 // contract is canceled 667 continue 668 } 669 contracts = append(contracts, contract) 670 } 671 672 // Get all the public keys and map them to contract ids. 673 pks := make([]types.SiaPublicKey, 0, len(allContracts)) 674 cids := make(map[string]types.FileContractID) 675 for _, contract := range contracts { 676 pks = append(pks, contract.HostPublicKey) 677 cids[contract.HostPublicKey.String()] = contract.ID 678 } 679 680 // Let the hostdb filter out bad hosts and cancel contracts with those 681 // hosts. 682 badHosts, err := c.staticHDB.CheckForIPViolations(pks) 683 if err != nil { 684 c.staticLog.Println("WARN: error checking for IP violations:", err) 685 return 686 } 687 for _, host := range badHosts { 688 if err := c.managedCancelContract(cids[host.String()]); err != nil { 689 c.staticLog.Print("WARNING: Wasn't able to cancel contract in managedPrunedRedundantAddressRange", err) 690 } 691 } 692 } 693 694 // managedLimitGFUHosts caps the number of GFU hosts to allowance.Hosts. 695 func (c *Contractor) managedLimitGFUHosts(contracts []skymodules.RenterContract, updates map[types.FileContractID]skymodules.ContractUtility, wantedHosts uint64) { 696 c.mu.Lock() 697 // Store the preferred contracts in a temporary map. If we have too many 698 // of them for the current allowance, we delete some. 699 preferredHosts := make(map[string]struct{}) 700 for hk := range c.preferredHosts { 701 if uint64(len(preferredHosts)) < wantedHosts { 702 preferredHosts[hk] = struct{}{} 703 } 704 } 705 c.mu.Unlock() 706 707 potentialHosts := make(map[string]struct{}) 708 for _, contract := range contracts { 709 // If the contract is !gfu ignore it. 710 if !contract.Utility.GoodForUpload { 711 continue 712 } 713 // If it is gfu, mark the corresponding host as a potential candidate. 714 potentialHosts[contract.HostPublicKey.String()] = struct{}{} 715 } 716 717 // If a preferred host is not part of the potential hosts, it's no longer 718 // preferred so we delete it. We also delete hosts that are in the preferred 719 // hosts from the potential ones. 720 for host := range preferredHosts { 721 _, exists := potentialHosts[host] 722 delete(potentialHosts, host) 723 if !exists { 724 delete(preferredHosts, host) 725 } 726 } 727 728 // Now we are only left with the preferred hosts that are gfu. 729 // If they are too many, trim them. 730 toTrim := len(preferredHosts) - int(wantedHosts) 731 for host := range preferredHosts { 732 if toTrim <= 0 { 733 break 734 } 735 delete(preferredHosts, host) 736 toTrim-- 737 } 738 // If there are too few, add some. 739 toAdd := int(wantedHosts) - len(preferredHosts) 740 if toAdd > 0 { 741 c.managedAddPreferredHosts(toAdd, preferredHosts, potentialHosts) 742 } 743 744 // Sanity check length of set. 745 if uint64(len(preferredHosts)) > wantedHosts { 746 build.Critical("too many contracts in the set of preferred contracts") 747 } 748 749 // Mark all contracts that are not in the preferred set as !gfu. 750 for _, contract := range contracts { 751 _, isPreferred := preferredHosts[contract.HostPublicKey.String()] 752 if isPreferred { 753 continue // nothing to do 754 } 755 if !contract.Utility.GoodForUpload { 756 continue // nothing to do 757 } 758 c.staticLog.Printf("[CONTRACTUTILITY][%v] marking contract as !GFU to take into accounts the cap defined by the allowance (update not applied yet), GFU: true -> false\n", contract.ID) 759 u := contract.Utility 760 u.GoodForUpload = false 761 updates[contract.ID] = u 762 } 763 764 // Update the preferred contracts. 765 c.mu.Lock() 766 c.preferredHosts = preferredHosts 767 c.mu.Unlock() 768 } 769 770 // staticCheckFormPaymentContractGouging will check whether the pricing from the 771 // host for forming a payment contract is too high to justify forming a contract 772 // with this host. 773 func staticCheckFormPaymentContractGouging(allowance skymodules.Allowance, hostSettings modules.HostExternalSettings) error { 774 // Check whether the RPC base price is too high. 775 if !allowance.MaxRPCPrice.IsZero() && allowance.MaxRPCPrice.Cmp(hostSettings.BaseRPCPrice) <= 0 { 776 return errors.New("rpc base price of host is too high - extortion protection enabled") 777 } 778 // Check whether the form contract price is too high. 779 if !allowance.MaxContractPrice.IsZero() && allowance.MaxContractPrice.Cmp(hostSettings.ContractPrice) <= 0 { 780 return errors.New("contract price of host is too high - extortion protection enabled") 781 } 782 // Check whether the sector access price is too high. 783 if !allowance.MaxSectorAccessPrice.IsZero() && allowance.MaxSectorAccessPrice.Cmp(hostSettings.SectorAccessPrice) <= 0 { 784 return errors.New("sector access price of host is too high - extortion protection enabled") 785 } 786 787 // Check whether the form contract price does not leave enough room for 788 // uploads and downloads. At least half of the payment contract's funds need 789 // to remain for download bandwidth. 790 if allowance.PaymentContractInitialFunding.Div64(2).Cmp(hostSettings.ContractPrice) <= 0 { 791 return errors.New("contract price of host is too high - extortion protection enabled") 792 } 793 return nil 794 } 795 796 // checkFormContractGouging will check whether the pricing for forming 797 // this contract triggers any price gouging warnings. 798 func checkFormContractGouging(allowance skymodules.Allowance, hostSettings modules.HostExternalSettings) error { 799 // Check whether the RPC base price is too high. 800 if !allowance.MaxRPCPrice.IsZero() && allowance.MaxRPCPrice.Cmp(hostSettings.BaseRPCPrice) < 0 { 801 return errors.New("rpc base price of host is too high - price gouging protection enabled") 802 } 803 // Check whether the form contract price is too high. 804 if !allowance.MaxContractPrice.IsZero() && allowance.MaxContractPrice.Cmp(hostSettings.ContractPrice) < 0 { 805 return errors.New("contract price of host is too high - price gouging protection enabled") 806 } 807 808 return nil 809 } 810 811 // managedRenew negotiates a new contract for data already stored with a host. 812 // It returns the new contract. This is a blocking call that performs network 813 // I/O. 814 func (c *Contractor) managedRenew(id types.FileContractID, a skymodules.Allowance, hpk types.SiaPublicKey, contractFunding types.Currency, newEndHeight types.BlockHeight, hostSettings modules.HostExternalSettings) (_ skymodules.RenterContract, err error) { 815 // Fetch the host associated with this contract. 816 host, ok, err := c.staticHDB.Host(hpk) 817 if err != nil { 818 return skymodules.RenterContract{}, errors.AddContext(err, "error getting host from hostdb:") 819 } 820 // Use the most recent hostSettings, along with the host db entry. 821 host.HostExternalSettings = hostSettings 822 823 if c.staticDeps.Disrupt("DefaultRenewSettings") { 824 c.staticLog.Debugln("Using default host settings") 825 host.HostExternalSettings = modules.DefaultHostExternalSettings() 826 // Reset some specific settings, not available through the default. 827 host.HostExternalSettings.NetAddress = hostSettings.NetAddress 828 host.HostExternalSettings.RemainingStorage = hostSettings.RemainingStorage 829 host.HostExternalSettings.TotalStorage = hostSettings.TotalStorage 830 host.HostExternalSettings.UnlockHash = hostSettings.UnlockHash 831 host.HostExternalSettings.RevisionNumber = hostSettings.RevisionNumber 832 host.HostExternalSettings.SiaMuxPort = hostSettings.SiaMuxPort 833 } 834 835 if reflect.DeepEqual(a, skymodules.Allowance{}) { 836 return skymodules.RenterContract{}, errors.New("called managedRenew but allowance isn't set") 837 } 838 period := a.Period 839 840 if !ok { 841 return skymodules.RenterContract{}, errHostNotFound 842 } else if host.Filtered { 843 return skymodules.RenterContract{}, errHostBlocked 844 } else if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 845 return skymodules.RenterContract{}, errTooExpensive 846 } else if host.MaxDuration < period { 847 return skymodules.RenterContract{}, errors.New("insufficient MaxDuration of host") 848 } 849 850 // cap host.MaxCollateral 851 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 852 host.MaxCollateral = maxCollateral 853 } 854 855 // Check for price gouging on the renewal. 856 err = checkFormContractGouging(a, host.HostExternalSettings) 857 if err != nil { 858 return skymodules.RenterContract{}, errors.AddContext(err, "unable to renew - price gouging protection enabled") 859 } 860 861 // get an address to use for negotiation 862 uc, err := c.staticWallet.NextAddress() 863 if err != nil { 864 return skymodules.RenterContract{}, err 865 } 866 defer func() { 867 if err != nil { 868 err = errors.Compose(err, c.staticWallet.MarkAddressUnused(uc)) 869 } 870 }() 871 872 // get the wallet seed 873 seed, _, err := c.staticWallet.PrimarySeed() 874 if err != nil { 875 return skymodules.RenterContract{}, err 876 } 877 // derive the renter seed and wipe it after we are done with it. 878 renterSeed := skymodules.DeriveRenterSeed(seed) 879 defer fastrand.Read(renterSeed[:]) 880 881 // create contract params 882 c.mu.RLock() 883 params := skymodules.ContractParams{ 884 Allowance: a, 885 Host: host, 886 Funding: contractFunding, 887 StartHeight: c.blockHeight, 888 EndHeight: newEndHeight, 889 RefundAddress: uc.UnlockHash(), 890 RenterSeed: renterSeed.EphemeralRenterSeed(newEndHeight), 891 } 892 c.mu.RUnlock() 893 894 // wipe the renter seed once we are done using it. 895 defer fastrand.Read(params.RenterSeed[:]) 896 897 // create a transaction builder with the correct amount of funding for the renewal. 898 txnBuilder, err := c.staticWallet.StartTransaction() 899 if err != nil { 900 return skymodules.RenterContract{}, err 901 } 902 err = txnBuilder.FundSiacoins(params.Funding) 903 if err != nil { 904 txnBuilder.Drop() // return unused outputs to wallet 905 return skymodules.RenterContract{}, err 906 } 907 // Add an output that sends all fund back to the refundAddress. 908 // Note that in order to send this transaction, a miner fee will have to be subtracted. 909 output := types.SiacoinOutput{ 910 Value: params.Funding, 911 UnlockHash: params.RefundAddress, 912 } 913 sweepTxn, sweepParents := txnBuilder.Sweep(output) 914 915 var newContract skymodules.RenterContract 916 var formationTxnSet []types.Transaction 917 if c.staticDeps.Disrupt("LegacyRenew") || build.VersionCmp(host.Version, "1.5.4") < 0 { 918 // Acquire the SafeContract. 919 oldContract, ok := c.staticContracts.Acquire(id) 920 if !ok { 921 return skymodules.RenterContract{}, errContractNotFound 922 } 923 if !oldContract.Utility().GoodForRenew { 924 return skymodules.RenterContract{}, errContractNotGFR 925 } 926 // RHP2 renewal. 927 newContract, formationTxnSet, err = c.staticContracts.Renew(oldContract, params, txnBuilder, c.staticTPool, c.staticHDB, c.staticTG.StopChan()) 928 c.staticContracts.Return(oldContract) 929 } else { 930 var w skymodules.Worker 931 w, err = c.staticWorkerPool.Worker(hpk) 932 if err != nil { 933 txnBuilder.Drop() // return unused outputs to wallet 934 return skymodules.RenterContract{}, err 935 } 936 newContract, formationTxnSet, err = w.RenewContract(c.staticTG.StopCtx(), id, params, txnBuilder) 937 } 938 if err != nil { 939 txnBuilder.Drop() // return unused outputs to wallet 940 return skymodules.RenterContract{}, err 941 } 942 943 monitorContractArgs := monitorContractArgs{ 944 false, 945 newContract.ID, 946 newContract.Transaction, 947 formationTxnSet, 948 sweepTxn, 949 sweepParents, 950 params.StartHeight, 951 } 952 err = c.staticWatchdog.callMonitorContract(monitorContractArgs) 953 if err != nil { 954 return skymodules.RenterContract{}, err 955 } 956 957 // Add a mapping from the contract's id to the public key of the host. This 958 // will destroy the previous mapping from pubKey to contract id but other 959 // modules are only interested in the most recent contract anyway. 960 c.mu.Lock() 961 c.pubKeysToContractID[newContract.HostPublicKey.String()] = newContract.ID 962 c.mu.Unlock() 963 964 // Update the hostdb to include the new contract. 965 err = c.staticHDB.UpdateContracts(c.staticContracts.ViewAll()) 966 if err != nil { 967 c.staticLog.Println("Unable to update hostdb contracts:", err) 968 } 969 970 return newContract, nil 971 } 972 973 // managedRenewContract will use the renew instructions to renew a contract, 974 // returning the amount of money that was put into the contract for renewal. 975 func (c *Contractor) managedRenewContract(renewInstructions fileContractRenewal, currentPeriod types.BlockHeight, allowance skymodules.Allowance, blockHeight, endHeight types.BlockHeight) (fundsSpent types.Currency, err error) { 976 if c.staticDeps.Disrupt("ContractRenewFail") { 977 err = errors.New("Renew failure due to dependency") 978 return 979 } 980 // Pull the variables out of the renewal. 981 id := renewInstructions.id 982 amount := renewInstructions.amount 983 hostPubKey := renewInstructions.hostPubKey 984 985 // Get a session with the host, before marking it as being renewed. 986 hs, err := c.Session(hostPubKey, c.staticTG.StopChan()) 987 if err != nil { 988 err = errors.AddContext(err, "Unable to establish session with host") 989 return 990 } 991 s := hs.(*hostSession) 992 993 // Mark the contract as being renewed, and defer logic to unmark it 994 // once renewing is complete. 995 c.staticLog.Debugln("Marking a contract for renew:", id) 996 c.mu.Lock() 997 c.renewing[id] = true 998 c.mu.Unlock() 999 defer func() { 1000 c.staticLog.Debugln("Unmarking the contract for renew", id) 1001 c.mu.Lock() 1002 delete(c.renewing, id) 1003 c.mu.Unlock() 1004 }() 1005 1006 // Wait for any active editors/downloaders/sessions to finish for this 1007 // contract, and then grab the latest host settings. 1008 var hostSettings modules.HostExternalSettings 1009 c.mu.RLock() 1010 e, eok := c.editors[id] 1011 d, dok := c.downloaders[id] 1012 c.mu.RUnlock() 1013 if eok { 1014 c.staticLog.Debugln("Waiting for editor invalidation") 1015 e.callInvalidate() 1016 c.staticLog.Debugln("Got editor invalidation") 1017 } 1018 if dok { 1019 c.staticLog.Debugln("Waiting for downloader invalidation") 1020 d.callInvalidate() 1021 c.staticLog.Debugln("Got downloader invalidation") 1022 } 1023 1024 // Use the Settings RPC with the host and then invalidate the session. 1025 hostSettings, err = s.Settings() 1026 if err != nil { 1027 err = errors.AddContext(err, "Unable to get host settings") 1028 return 1029 } 1030 c.staticLog.Debugln("Waiting for session invalidation") 1031 s.callInvalidate() 1032 c.staticLog.Debugln("Got session invalidation") 1033 1034 // Perform the actual renew. If the renew fails, return the 1035 // contract. If the renew fails we check how often it has failed 1036 // before. Once it has failed for a certain number of blocks in a 1037 // row and reached its second half of the renew window, we give up 1038 // on renewing it and set goodForRenew to false. 1039 c.staticLog.Debugln("calling managedRenew on contract", id) 1040 newContract, errRenew := c.managedRenew(id, allowance, hostPubKey, amount, endHeight, hostSettings) 1041 c.staticLog.Debugln("managedRenew has returned with error:", errRenew) 1042 oldContract, exists := c.staticContracts.Acquire(id) 1043 if !exists { 1044 return types.ZeroCurrency, errors.AddContext(errContractNotFound, "failed to acquire oldContract after renewal") 1045 } 1046 oldUtility := oldContract.Utility() 1047 md := oldContract.Metadata() 1048 if errRenew != nil { 1049 // Increment the number of failed renews for the contract if it 1050 // was the host's fault. 1051 if skymodules.IsHostsFault(errRenew) { 1052 c.mu.Lock() 1053 c.numFailedRenews[oldContract.Metadata().ID]++ 1054 totalFailures := c.numFailedRenews[oldContract.Metadata().ID] 1055 c.mu.Unlock() 1056 c.staticLog.Debugln("remote host determined to be at fault, tallying up failed renews", totalFailures, id) 1057 } 1058 1059 // Check if contract has to be replaced. 1060 c.mu.RLock() 1061 numRenews, failedBefore := c.numFailedRenews[md.ID] 1062 c.mu.RUnlock() 1063 secondHalfOfWindow := blockHeight+allowance.RenewWindow/2 >= md.EndHeight 1064 replace := numRenews >= ConsecutiveRenewalsBeforeReplacement 1065 if failedBefore && secondHalfOfWindow && replace { 1066 errorMsg := fmt.Sprintf("[CONTRACTUTILITY][%v] WARN: consistently failed to renew contract for host %v, marked as bad and locked: %v, GFU: %v -> false, GFR: %v -> false, GFRef: %v -> false", 1067 md.ID, md.HostPublicKey, errRenew, oldUtility.GoodForUpload, oldUtility.GoodForRenew, oldUtility.GoodForRefresh) 1068 1069 oldUtility.GoodForRefresh = false 1070 oldUtility.GoodForRenew = false 1071 oldUtility.GoodForUpload = false 1072 oldUtility.Locked = true 1073 err := c.callUpdateUtility(oldContract, oldUtility, true) 1074 if err != nil { 1075 errorMsg += fmt.Sprintf(", failed to update utility: %v", err) 1076 } 1077 c.staticLog.Println(errorMsg) 1078 c.staticContracts.Return(oldContract) 1079 return types.ZeroCurrency, errors.AddContext(errRenew, "contract marked as bad for too many consecutive failed renew attempts") 1080 } 1081 1082 // Seems like it doesn't have to be replaced yet. Log the 1083 // failure and number of renews that have failed so far. 1084 c.staticLog.Printf("WARN: failed to renew contract %v [%v]: '%v', current height: %v, proposed end height: %v, max duration: %v", 1085 oldContract.Metadata().HostPublicKey, numRenews, errRenew, blockHeight, endHeight, hostSettings.MaxDuration) 1086 c.staticContracts.Return(oldContract) 1087 return types.ZeroCurrency, errors.AddContext(errRenew, "contract renewal with host was unsuccessful") 1088 } 1089 c.staticLog.Printf("Renewed contract %v\n", id) 1090 1091 // Skip the deletion of the old contract if required and delete the new 1092 // contract to make sure we keep using the old one even though it has been 1093 // finalized. 1094 if c.staticDeps.Disrupt("SkipContractDeleteAfterRenew") { 1095 c.staticContracts.Return(oldContract) 1096 newSC, ok := c.staticContracts.Acquire(newContract.ID) 1097 if ok { 1098 c.staticContracts.Delete(newSC) 1099 } 1100 return amount, nil 1101 } 1102 1103 // Update the utility values for the old contract. We ignore the churn 1104 // here because we renewed the contract successfully. So we don't want 1105 // to consider this churn. 1106 msg := fmt.Sprintf("[CONTRACTUTILITY][%v] updating old contract utility after successful renew, locked: %v -> true, GFU: %v -> false, GFR: %v -> false, GFRef: %v -> false", md.ID, oldUtility.Locked, oldUtility.GoodForUpload, oldUtility.GoodForRenew, oldUtility.GoodForRefresh) 1107 oldUtility.GoodForRefresh = false 1108 oldUtility.GoodForRenew = false 1109 oldUtility.GoodForUpload = false 1110 oldUtility.Locked = true 1111 if err := c.callUpdateUtility(oldContract, oldUtility, true); err != nil { 1112 c.staticLog.Println("Failed to update the contract utilities", err) 1113 c.staticContracts.Return(oldContract) 1114 return amount, nil // Error is not returned because the renew succeeded. 1115 } 1116 c.staticLog.Println(msg) 1117 1118 if c.staticDeps.Disrupt("InterruptContractSaveToDiskAfterDeletion") { 1119 c.staticContracts.Return(oldContract) 1120 return amount, errors.New("InterruptContractSaveToDiskAfterDeletion disrupt") 1121 } 1122 // Lock the contractor as we update it to use the new contract 1123 // instead of the old contract. 1124 c.mu.Lock() 1125 // Link Contracts 1126 c.renewedFrom[newContract.ID] = id 1127 c.renewedTo[id] = newContract.ID 1128 // Store the contract in the record of historic contracts. 1129 c.oldContracts[id] = oldContract.Metadata() 1130 // Save the contractor. 1131 err = c.save() 1132 if err != nil { 1133 c.staticLog.Println("Failed to save the contractor after creating a new contract.") 1134 } 1135 c.mu.Unlock() 1136 // Delete the old contract. 1137 c.staticContracts.Delete(oldContract) 1138 1139 // Signal to the watchdog that it should immediately post the last 1140 // revision for this contract. 1141 go c.staticWatchdog.threadedSendMostRecentRevision(oldContract.Metadata()) 1142 return amount, nil 1143 } 1144 1145 // managedFindRecoverableContracts will spawn a thread to rescan parts of the 1146 // blockchain for recoverable contracts if the wallet has been locked during the 1147 // last scan. 1148 func (c *Contractor) managedFindRecoverableContracts() { 1149 if c.staticDeps.Disrupt("disableAutomaticContractRecoveryScan") { 1150 return 1151 } 1152 c.mu.RLock() 1153 cc := c.recentRecoveryChange 1154 c.mu.RUnlock() 1155 if err := c.callInitRecoveryScan(cc); err != nil { 1156 c.staticLog.Debug(err) 1157 return 1158 } 1159 } 1160 1161 // managedAcquireAndUpdateContractUtility is a helper function that acquires a 1162 // contract, updates its ContractUtility and returns the contract again. 1163 // 'notChurn' needs to be 'true' for contracts which changed to !gfr but 1164 // shouldn't be considered churn in the churnLimiter. e.g. if a user canceled a 1165 // contract manually. 1166 func (c *Contractor) managedAcquireAndUpdateContractUtility(id types.FileContractID, utility skymodules.ContractUtility, notChurn bool) error { 1167 safeContract, ok := c.staticContracts.Acquire(id) 1168 if !ok { 1169 return errors.New("failed to acquire contract for update") 1170 } 1171 defer c.staticContracts.Return(safeContract) 1172 1173 return c.managedUpdateContractUtilityWithNotChurn(safeContract, utility, notChurn) 1174 } 1175 1176 // managedUpdateContractUtility is a helper function that updates the contract 1177 // with the given utility. 'notChurn' needs to be 'true' for contracts which 1178 // changed to !gfr but shouldn't be considered churn in the churnLimiter. e.g. 1179 // if a user canceled a contract manually. 1180 func (c *Contractor) managedUpdateContractUtilityWithNotChurn(safeContract *proto.SafeContract, utility skymodules.ContractUtility, notChurn bool) error { 1181 // Sanity check to verify that we aren't attempting to set a good utility on 1182 // a contract that has been renewed. 1183 c.mu.Lock() 1184 _, exists := c.renewedTo[safeContract.Metadata().ID] 1185 c.mu.Unlock() 1186 if exists && (utility.GoodForRenew || utility.GoodForUpload) { 1187 c.staticLog.Critical("attempting to update contract utility on a contract that has been renewed") 1188 } 1189 1190 return c.callUpdateUtility(safeContract, utility, notChurn) 1191 } 1192 1193 // managedUpdateContractUtility is a helper function that updates the contract 1194 // with the given utility. 1195 func (c *Contractor) managedUpdateContractUtility(safeContract *proto.SafeContract, utility skymodules.ContractUtility) error { 1196 // Default behaviour for updating the utility is to consider it churn. 1197 return c.managedUpdateContractUtilityWithNotChurn(safeContract, utility, false) 1198 } 1199 1200 // callUpdateUtility updates the utility of a contract and notifies the 1201 // churnLimiter of churn if necessary. This method should *always* be used as 1202 // opposed to calling UpdateUtility directly on a safe contract from the 1203 // contractor. Pass in renewed as true if the contract has been renewed and is 1204 // not churn. 1205 func (c *Contractor) callUpdateUtility(safeContract *proto.SafeContract, newUtility skymodules.ContractUtility, notChurn bool) error { 1206 contract := safeContract.Metadata() 1207 1208 // If the contract is going from GFR to !GFR, notify the churn limiter. 1209 if !notChurn && contract.Utility.GoodForRenew && !newUtility.GoodForRenew { 1210 c.staticChurnLimiter.callNotifyChurnedContract(contract) 1211 } 1212 return safeContract.UpdateUtility(newUtility) 1213 } 1214 1215 // threadedContractMaintenance checks the set of contracts that the contractor 1216 // has against the allownace, renewing any contracts that need to be renewed, 1217 // dropping contracts which are no longer worthwhile, and adding contracts if 1218 // there are not enough. 1219 // 1220 // Between each network call, the thread checks whether a maintenance interrupt 1221 // signal is being sent. If so, maintenance returns, yielding to whatever thread 1222 // issued the interrupt. 1223 func (c *Contractor) threadedContractMaintenance() { 1224 err := c.staticTG.Add() 1225 if err != nil { 1226 return 1227 } 1228 defer c.staticTG.Done() 1229 1230 // No contract maintenance unless contractor is synced. 1231 if !c.managedSynced() { 1232 c.staticLog.Debugln("Skipping contract maintenance since consensus isn't synced yet") 1233 return 1234 } 1235 c.staticLog.Debugln("starting contract maintenance") 1236 1237 // Only one instance of this thread should be running at a time. Under 1238 // normal conditions, fine to return early if another thread is already 1239 // doing maintenance. The next block will trigger another round. Under 1240 // testing, control is insufficient if the maintenance loop isn't guaranteed 1241 // to run. 1242 if build.Release == "testing" { 1243 c.maintenanceLock.Lock() 1244 } else if !c.maintenanceLock.TryLock() { 1245 c.staticLog.Debugln("maintenance lock could not be obtained") 1246 return 1247 } 1248 defer c.maintenanceLock.Unlock() 1249 1250 // Register the WalletLockedDuringMaintenance alert if necessary. 1251 var registerWalletLockedDuringMaintenance bool 1252 defer func() { 1253 if registerWalletLockedDuringMaintenance { 1254 c.staticAlerter.RegisterAlert(modules.AlertIDWalletLockedDuringMaintenance, AlertMSGWalletLockedDuringMaintenance, modules.ErrLockedWallet.Error(), modules.SeverityWarning) 1255 } else { 1256 c.staticAlerter.UnregisterAlert(modules.AlertIDWalletLockedDuringMaintenance) 1257 } 1258 }() 1259 1260 // Fetch the allowance in the beginning of the contract maintenance and 1261 // make sure we are using this one during all of it to avoid race 1262 // conditions. 1263 c.mu.RLock() 1264 allowance := c.allowance 1265 blockHeight := c.blockHeight 1266 currentPeriod := c.currentPeriod 1267 endHeight := c.contractEndHeight() 1268 c.mu.RUnlock() 1269 1270 // Perform general cleanup of the contracts. This includes recovering lost 1271 // contracts, archiving contracts, and other cleanup work. This should all 1272 // happen before the rest of the maintenance. 1273 c.managedFindRecoverableContracts() 1274 c.callRecoverContracts() 1275 c.managedArchiveContracts() 1276 c.managedCheckForDuplicates() 1277 c.managedUpdatePubKeyToContractIDMap() 1278 c.managedPrunedRedundantAddressRange() 1279 err = c.managedMarkContractsUtility(allowance.Hosts, endHeight) 1280 if err != nil { 1281 c.staticLog.Println("Unable to mark contract utilities:", err) 1282 return 1283 } 1284 err = c.staticHDB.UpdateContracts(c.staticContracts.ViewAll()) 1285 if err != nil { 1286 c.staticLog.Println("Unable to update hostdb contracts:", err) 1287 return 1288 } 1289 1290 // If there are no hosts requested by the allowance, there is no remaining 1291 // work. 1292 wantedHosts := allowance.Hosts 1293 if wantedHosts <= 0 { 1294 c.staticLog.Debugln("Exiting contract maintenance because the number of desired hosts is <= zero.") 1295 return 1296 } 1297 1298 // Get the number of gfrContracts we have as well as the data they hold. 1299 var gfrContracts uint64 1300 var gfrData uint64 1301 for _, c := range c.staticContracts.ViewAll() { 1302 if c.Utility.GoodForRenew { 1303 gfrContracts++ 1304 gfrData += c.Size() 1305 } 1306 } 1307 1308 // Calculate the anticipated transaction fee. 1309 _, maxFee := c.staticTPool.FeeEstimation() 1310 txnFee := maxFee.Mul64(skymodules.EstimatedFileContractTransactionSetSize) 1311 1312 // Create the renewSet and refreshSet. Each is a list of contracts that need 1313 // to be renewed, paired with the amount of money to use in each renewal. 1314 // 1315 // The renewSet is specifically contracts which are being renewed because 1316 // they are about to expire. And the refreshSet is contracts that are being 1317 // renewed because they are out of money. 1318 // 1319 // The contractor will prioritize contracts in the renewSet over contracts 1320 // in the refreshSet. If the wallet does not have enough money, or if the 1321 // allowance does not have enough money, the contractor will prefer to save 1322 // data in the long term rather than renew a contract. 1323 var renewSet []decoratedFileContractRenewal 1324 var refreshSet []decoratedFileContractRenewal 1325 1326 // Sanity check that by the end of the contract maintenance, we don't 1327 // have more than allowance.Hosts gfu contracts. 1328 if build.Release == "testing" { 1329 defer func() { 1330 var gfuContracts uint64 1331 for _, c := range c.staticContracts.ViewAll() { 1332 if c.Utility.GoodForUpload { 1333 gfuContracts++ 1334 } 1335 } 1336 if gfuContracts > allowance.Hosts { 1337 build.Critical(fmt.Sprintf("threadedContractMaintenance: got %v > %v gfu hosts after maintenance (renew/refresh %v/%v)", gfuContracts, allowance.Hosts, len(renewSet), len(refreshSet))) 1338 } 1339 }() 1340 } 1341 1342 // Iterate through the contracts again, figuring out which contracts to 1343 // renew and how much extra funds to renew them with. 1344 for _, contract := range c.staticContracts.ViewAll() { 1345 c.staticLog.Debugln("Examining a contract:", contract.HostPublicKey, contract.ID) 1346 // Skip any host that does not match our whitelist/blacklist filter 1347 // settings. 1348 host, _, err := c.staticHDB.Host(contract.HostPublicKey) 1349 if err != nil { 1350 c.staticLog.Println("WARN: error getting host", err) 1351 continue 1352 } 1353 if host.Filtered { 1354 c.staticLog.Debugln("Contract skipped because it is filtered") 1355 continue 1356 } 1357 // Skip hosts that can't use the current renter-host protocol. 1358 if build.VersionCmp(host.Version, modules.MinimumSupportedRenterHostProtocolVersion) < 0 { 1359 c.staticLog.Debugln("Contract skipped because host is using an outdated version", host.Version) 1360 continue 1361 } 1362 1363 // Skip any contracts which do not exist or are otherwise unworthy for 1364 // renewal. 1365 utility, ok := c.managedContractUtility(contract.ID) 1366 if !ok || (!utility.GoodForRenew && !utility.GoodForRefresh) { 1367 if blockHeight-contract.StartHeight < types.BlocksPerWeek { 1368 c.staticLog.Debugln("Contract did not last 1 week and is not being renewed", contract.ID) 1369 } 1370 c.staticLog.Debugln("Contract skipped because it is not good for renew nor refresh (utility.GoodForRenew, utility.GoodForRefresh exists)", utility.GoodForRenew, utility.GoodForRefresh, ok) 1371 continue 1372 } 1373 goodForRenew := utility.GoodForRenew 1374 goodForRefresh := utility.GoodForRefresh 1375 1376 // If the contract needs to be renewed because it is about to expire, 1377 // calculate a spending for the contract that is proportional to how 1378 // much money was spend on the contract throughout this billing cycle 1379 // (which is now ending). 1380 if goodForRenew && blockHeight+allowance.RenewWindow >= contract.EndHeight && !c.staticDeps.Disrupt("disableRenew") { 1381 renewAmount, err := c.managedEstimateRenewFundingRequirements(contract, gfrContracts, blockHeight, allowance) 1382 if err != nil { 1383 c.staticLog.Debugln("Contract skipped because there was an error estimating renew funding requirements", renewAmount, err) 1384 continue 1385 } 1386 1387 renewSet = append(renewSet, decoratedFileContractRenewal{ 1388 fileContractRenewal{ 1389 id: contract.ID, 1390 amount: renewAmount, 1391 data: contract.Size(), 1392 hostPubKey: contract.HostPublicKey, 1393 }, 1394 isLateRenewal(allowance, contract, blockHeight), 1395 }) 1396 c.staticLog.Debugln("Contract has been added to the renew set for being past the renew height") 1397 continue 1398 } 1399 1400 // Check if the contract is empty. We define a contract as being empty 1401 // if less than 'minContractFundRenewalThreshold' funds are remaining 1402 // (3% at time of writing), or if there is less than 3 sectors worth of 1403 // storage+upload+download remaining. 1404 blockBytes := types.NewCurrency64(modules.SectorSize * uint64(allowance.Period)) 1405 sectorStoragePrice := host.StoragePrice.Mul(blockBytes) 1406 sectorUploadBandwidthPrice := host.UploadBandwidthPrice.Mul64(modules.SectorSize) 1407 sectorDownloadBandwidthPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize) 1408 sectorBandwidthPrice := sectorUploadBandwidthPrice.Add(sectorDownloadBandwidthPrice) 1409 sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice) 1410 percentRemaining, _ := big.NewRat(0, 1).SetFrac(contract.RenterFunds.Big(), contract.TotalCost.Big()).Float64() 1411 lowFundsRefresh := c.staticDeps.Disrupt("LowFundsRefresh") 1412 if goodForRefresh && (lowFundsRefresh || ((contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0 || percentRemaining < MinContractFundRenewalThreshold) && !c.staticDeps.Disrupt("disableRenew"))) { 1413 // Renew the contract with double the amount of funds that the 1414 // contract had previously. The reason that we double the funding 1415 // instead of doing anything more clever is that we don't know what 1416 // the usage pattern has been. The spending could have all occurred 1417 // in one burst recently, and the user might need a contract that 1418 // has substantially more money in it. 1419 // 1420 // We double so that heavily used contracts can grow in funding 1421 // quickly without consuming too many transaction fees, however this 1422 // does mean that a larger percentage of funds get locked away from 1423 // the user in the event that the user stops uploading immediately 1424 // after the renew. 1425 refreshAmount := contract.TotalCost.Mul64(2) 1426 minInitialContractFunds := allowance.Funds.Div64(allowance.Hosts).Div64(MinInitialContractFundingDivFactor) 1427 minimum := initialContractFunding(allowance, host, txnFee, minInitialContractFunds, types.ZeroCurrency) 1428 if refreshAmount.Cmp(minimum) < 0 { 1429 refreshAmount = minimum 1430 c.staticLog.Printf("Contract refresh amount %v below minimum amount %v", refreshAmount, minimum) 1431 } 1432 refreshSet = append(refreshSet, decoratedFileContractRenewal{ 1433 fileContractRenewal{ 1434 id: contract.ID, 1435 amount: refreshAmount, 1436 data: contract.Size(), 1437 hostPubKey: contract.HostPublicKey, 1438 }, 1439 isLateRenewal(allowance, contract, blockHeight), 1440 }) 1441 c.staticLog.Debugln("Contract identified as needing to be added to refresh set", contract.RenterFunds, sectorPrice.Mul64(3), percentRemaining, MinContractFundRenewalThreshold) 1442 } else { 1443 c.staticLog.Debugln("Contract did not get added to the refresh set", contract.RenterFunds, sectorPrice.Mul64(3), percentRemaining, MinContractFundRenewalThreshold) 1444 } 1445 } 1446 if len(renewSet) != 0 || len(refreshSet) != 0 { 1447 c.staticLog.Printf("renewing %v contracts and refreshing %v contracts", len(renewSet), len(refreshSet)) 1448 } 1449 1450 // Update the failed renew map so that it only contains contracts which we 1451 // are currently trying to renew or refresh. The failed renew map is a map 1452 // that we use to track how many times consecutively we failed to renew a 1453 // contract with a host, so that we know if we need to abandon that host. 1454 c.mu.Lock() 1455 newFirstFailedRenew := make(map[types.FileContractID]types.BlockHeight) 1456 for _, r := range renewSet { 1457 if _, exists := c.numFailedRenews[r.id]; exists { 1458 newFirstFailedRenew[r.id] = c.numFailedRenews[r.id] 1459 } 1460 } 1461 for _, r := range refreshSet { 1462 if _, exists := c.numFailedRenews[r.id]; exists { 1463 newFirstFailedRenew[r.id] = c.numFailedRenews[r.id] 1464 } 1465 } 1466 c.numFailedRenews = newFirstFailedRenew 1467 c.mu.Unlock() 1468 1469 // Depend on the PeriodSpending function to get a breakdown of spending in 1470 // the contractor. Then use that to determine how many funds remain 1471 // available in the allowance for renewals. 1472 spending, err := c.PeriodSpending() 1473 if err != nil { 1474 // This should only error if the contractor is shutting down 1475 c.staticLog.Println("WARN: error getting period spending:", err) 1476 return 1477 } 1478 var fundsRemaining types.Currency 1479 // Check for an underflow. This can happen if the user reduced their 1480 // allowance at some point to less than what we've already spent. 1481 if spending.TotalAllocated.Cmp(allowance.Funds) < 0 { 1482 fundsRemaining = allowance.Funds.Sub(spending.TotalAllocated) 1483 } 1484 c.staticLog.Debugln("Remaining funds in allowance:", fundsRemaining.HumanString()) 1485 1486 // Keep track of the amount of data that failed to renew. 1487 // 1488 // NOTE: we only count the failed renew data if the renew window leeway has 1489 // passed already 1490 var numRenewFails int 1491 var failedRenewData uint64 1492 1493 // Register or unregister and alerts related to contract renewal or 1494 // formation. 1495 var registerLowFundsAlert bool 1496 var renewErr error 1497 defer func() { 1498 if registerLowFundsAlert { 1499 c.staticAlerter.RegisterAlert(modules.AlertIDRenterAllowanceLowFunds, AlertMSGAllowanceLowFunds, AlertCauseInsufficientAllowanceFunds, modules.SeverityWarning) 1500 } else { 1501 c.staticAlerter.UnregisterAlert(modules.AlertIDRenterAllowanceLowFunds) 1502 } 1503 1504 alertSeverity := modules.SeverityError 1505 // Increase the alert severity for renewal fails to critical if the 1506 // number of data which might be lost is more than 20% of the total data 1507 // in gfr contracts. 1508 if float64(failedRenewData) > math.Ceil(float64(gfrData)*MaxCriticalRenewFailThreshold) { 1509 alertSeverity = modules.SeverityCritical 1510 } 1511 if renewErr != nil { 1512 c.staticLog.Debugln("SEVERE", numRenewFails, float64(allowance.Hosts)*MaxCriticalRenewFailThreshold) 1513 c.staticLog.Debugln("alert err: ", renewErr) 1514 alertMSG := fmt.Sprintf("%v out of %v renewals failed (%v bytes out of %v) - number of total gfr contracts is %v - see contractor.log for details", numRenewFails, len(renewSet)+len(refreshSet), failedRenewData, gfrData, gfrContracts) 1515 c.staticAlerter.RegisterAlert(modules.AlertIDRenterContractRenewalError, AlertMSGFailedContractRenewal, alertMSG, modules.AlertSeverity(alertSeverity)) 1516 } else { 1517 c.staticAlerter.UnregisterAlert(modules.AlertIDRenterContractRenewalError) 1518 } 1519 }() 1520 // Go through the contracts we've assembled for renewal. Any contracts that 1521 // need to be renewed because they are expiring (renewSet) get priority over 1522 // contracts that need to be renewed because they have exhausted their funds 1523 // (refreshSet). If there is not enough money available, the more expensive 1524 // contracts will be skipped. 1525 for _, renewal := range renewSet { 1526 // Return here if an interrupt or kill signal has been sent. 1527 select { 1528 case <-c.staticTG.StopChan(): 1529 c.staticLog.Println("returning because the renter was stopped") 1530 return 1531 case <-c.staticInterruptMaintenance: 1532 c.staticLog.Println("returning because maintenance was interrupted") 1533 return 1534 default: 1535 } 1536 1537 unlocked, err := c.staticWallet.Unlocked() 1538 if !unlocked || err != nil { 1539 registerWalletLockedDuringMaintenance = true 1540 c.staticLog.Println("Contractor is attempting to renew contracts that are about to expire, however the wallet is locked") 1541 return 1542 } 1543 1544 c.staticLog.Println("Attempting to perform a renewal:", renewal.id) 1545 // Skip this renewal if we don't have enough funds remaining. 1546 if renewal.amount.Cmp(fundsRemaining) > 0 || c.staticDeps.Disrupt("LowFundsRenewal") { 1547 c.staticLog.Println("Skipping renewal because there are not enough funds remaining in the allowance", renewal.id, renewal.amount, fundsRemaining) 1548 registerLowFundsAlert = true 1549 continue 1550 } 1551 1552 // Renew one contract. The error is ignored because the renew function 1553 // already will have logged the error, and in the event of an error, 1554 // 'fundsSpent' will return '0'. 1555 fundsSpent, err := c.managedRenewContract(renewal.fileContractRenewal, currentPeriod, allowance, blockHeight, endHeight) 1556 if errors.Contains(err, errContractNotGFR) { 1557 // Do not add a renewal error. 1558 c.staticLog.Debugln("Contract skipped because it is not good for renew", renewal.id) 1559 } else if err != nil { 1560 c.staticLog.Println("Error renewing a contract", renewal.id, err) 1561 renewErr = errors.Compose(renewErr, err) 1562 numRenewFails++ 1563 if renewal.lateRenewal { 1564 failedRenewData += renewal.data 1565 } 1566 } else { 1567 c.staticLog.Println("Renewal completed without error") 1568 } 1569 fundsRemaining = fundsRemaining.Sub(fundsSpent) 1570 } 1571 for _, renewal := range refreshSet { 1572 // Return here if an interrupt or kill signal has been sent. 1573 select { 1574 case <-c.staticTG.StopChan(): 1575 c.staticLog.Println("returning because the renter was stopped") 1576 return 1577 case <-c.staticInterruptMaintenance: 1578 c.staticLog.Println("returning because maintenance was interrupted") 1579 return 1580 default: 1581 } 1582 1583 unlocked, err := c.staticWallet.Unlocked() 1584 if !unlocked || err != nil { 1585 registerWalletLockedDuringMaintenance = true 1586 c.staticLog.Println("contractor is attempting to refresh contracts that have run out of funds, however the wallet is locked") 1587 return 1588 } 1589 1590 // Skip this renewal if we don't have enough funds remaining. 1591 c.staticLog.Debugln("Attempting to perform a contract refresh:", renewal.id) 1592 if renewal.amount.Cmp(fundsRemaining) > 0 || c.staticDeps.Disrupt("LowFundsRefresh") { 1593 c.staticLog.Println("skipping refresh because there are not enough funds remaining in the allowance", renewal.amount.HumanString(), fundsRemaining.HumanString()) 1594 registerLowFundsAlert = true 1595 continue 1596 } 1597 1598 // Renew one contract. The error is ignored because the renew function 1599 // already will have logged the error, and in the event of an error, 1600 // 'fundsSpent' will return '0'. 1601 fundsSpent, err := c.managedRenewContract(renewal.fileContractRenewal, currentPeriod, allowance, blockHeight, endHeight) 1602 if err != nil { 1603 c.staticLog.Println("Error refreshing a contract", renewal.id, err) 1604 renewErr = errors.Compose(renewErr, err) 1605 numRenewFails++ 1606 if renewal.lateRenewal { 1607 failedRenewData += renewal.data 1608 } 1609 } else { 1610 c.staticLog.Println("Refresh completed without error") 1611 } 1612 fundsRemaining = fundsRemaining.Sub(fundsSpent) 1613 } 1614 1615 // Get Hosts for contract formation. 1616 var hosts []skymodules.HostDBEntry 1617 var neededContracts int 1618 if allowance.PortalMode() { 1619 neededContracts, hosts = c.managedHostsForPortalFormation(allowance) 1620 } else { 1621 neededContracts, hosts = c.managedHostsForRegularFormation(allowance) 1622 } 1623 1624 // Form contracts. 1625 lf, wl := c.managedFormContracts(fundsRemaining, hosts, neededContracts, allowance, endHeight) 1626 1627 // Register alerts if necessary. 1628 registerLowFundsAlert = registerLowFundsAlert || lf 1629 registerWalletLockedDuringMaintenance = registerWalletLockedDuringMaintenance || wl 1630 } 1631 1632 // managedHostsForPortalFormation returns the hosts to form contracts with for a 1633 // portal. 1634 func (c *Contractor) managedHostsForPortalFormation(allowance skymodules.Allowance) (int, []skymodules.HostDBEntry) { 1635 hosts, err := c.staticHDB.ActiveHosts() 1636 if err != nil { 1637 c.staticLog.Printf("Error fetching list of active hosts when attempting to form view contracts: %v", err) 1638 return 0, nil 1639 } 1640 return hostsForPortalFormation(allowance, c.staticContracts.ViewAll(), c.RecoverableContracts(), hosts, c.staticLog, c.staticHDB.ScoreBreakdown) 1641 } 1642 1643 // managedHostsForRegularFormation returns the number of hosts needed for 1644 // non-portal contract formation plus a set of hosts to use. 1645 func (c *Contractor) managedHostsForRegularFormation(allowance skymodules.Allowance) (int, []skymodules.HostDBEntry) { 1646 return hostsForRegularFormation(allowance, c.staticContracts.ViewAll(), c.RecoverableContracts(), c.staticHDB.RandomHosts, c.staticLog) 1647 } 1648 1649 // managedFormContracts tries to form up to neededContracts with the hosts given 1650 // by hosts and the provided budget, allowance and endHeight. 1651 func (c *Contractor) managedFormContracts(budget types.Currency, hosts []skymodules.HostDBEntry, neededContracts int, allowance skymodules.Allowance, endHeight types.BlockHeight) (lowFunds, walletLocked bool) { 1652 // Calculate the anticipated transaction fee. 1653 _, maxFee := c.staticTPool.FeeEstimation() 1654 txnFee := maxFee.Mul64(skymodules.EstimatedFileContractTransactionSetSize) 1655 1656 // Determine the max and min initial contract funding based on the allowance 1657 // settings 1658 maxInitialContractFunds := allowance.Funds.Div64(allowance.Hosts).Mul64(MaxInitialContractFundingMulFactor).Div64(MaxInitialContractFundingDivFactor) 1659 minInitialContractFunds := allowance.Funds.Div64(allowance.Hosts).Div64(MinInitialContractFundingDivFactor) 1660 1661 // Get a list of all current contracts. 1662 currentContracts := make(map[string]skymodules.RenterContract) 1663 gfuContracts := 0 1664 for _, contract := range c.staticContracts.ViewAll() { 1665 currentContracts[contract.HostPublicKey.String()] = contract 1666 if contract.Utility.GoodForUpload { 1667 gfuContracts++ 1668 } 1669 } 1670 1671 // Compute how many gfu contracts we still need. 1672 remainingGFU := 0 1673 if gfuContracts < int(allowance.Hosts) { 1674 remainingGFU = int(allowance.Hosts) - gfuContracts 1675 } 1676 1677 // Form contracts with the hosts one at a time, until we have enough 1678 // contracts. 1679 for _, host := range hosts { 1680 // Return here if an interrupt or kill signal has been sent. 1681 select { 1682 case <-c.staticTG.StopChan(): 1683 c.staticLog.Println("returning because the renter was stopped") 1684 return 1685 case <-c.staticInterruptMaintenance: 1686 c.staticLog.Println("returning because maintenance was interrupted") 1687 return 1688 default: 1689 } 1690 1691 // If no more contracts are needed, break. 1692 if neededContracts <= 0 { 1693 break 1694 } 1695 1696 // Calculate the contract funding with host 1697 contractFunds := initialContractFunding(allowance, host, txnFee, minInitialContractFunds, maxInitialContractFunds) 1698 1699 // Confirm the wallet is still unlocked 1700 unlocked, err := c.staticWallet.Unlocked() 1701 if !unlocked || err != nil { 1702 walletLocked = true 1703 c.staticLog.Println("contractor is attempting to establish new contracts with hosts, however the wallet is locked") 1704 return 1705 } 1706 1707 // Determine if we have enough money to form a new contract. 1708 if budget.Cmp(contractFunds) < 0 || c.staticDeps.Disrupt("LowFundsFormation") { 1709 lowFunds = true 1710 c.staticLog.Println("WARN: need to form new contracts, but unable to because of a low allowance") 1711 break 1712 } 1713 1714 // If we are using a custom resolver we need to replace the domain name 1715 // with 127.0.0.1 to be able to form contracts. 1716 if c.staticDeps.Disrupt("customResolver") { 1717 port := host.NetAddress.Port() 1718 host.NetAddress = modules.NetAddress(fmt.Sprintf("127.0.0.1:%s", port)) 1719 } 1720 1721 // Attempt forming a contract with this host. 1722 start := time.Now() 1723 fundsSpent, newContract, err := c.managedNewContract(host, allowance, contractFunds, endHeight) 1724 if err != nil { 1725 c.staticLog.Printf("Attempted to form a contract with %v, time spent %v, but negotiation failed: %v\n", host.NetAddress, time.Since(start).Round(time.Millisecond), err) 1726 continue 1727 } 1728 budget = budget.Sub(fundsSpent) 1729 neededContracts-- 1730 1731 sb, err := c.staticHDB.ScoreBreakdown(host) 1732 if err == nil { 1733 c.staticLog.Println("A new contract has been formed with a host:", newContract.ID) 1734 c.staticLog.Println("Score: ", sb.Score) 1735 c.staticLog.Println("Age Adjustment: ", sb.AgeAdjustment) 1736 c.staticLog.Println("Base Price Adjustment: ", sb.BasePriceAdjustment) 1737 c.staticLog.Println("Burn Adjustment: ", sb.BurnAdjustment) 1738 c.staticLog.Println("Collateral Adjustment: ", sb.CollateralAdjustment) 1739 c.staticLog.Println("Duration Adjustment: ", sb.DurationAdjustment) 1740 c.staticLog.Println("Interaction Adjustment:", sb.InteractionAdjustment) 1741 c.staticLog.Println("Price Adjustment: ", sb.PriceAdjustment) 1742 c.staticLog.Println("Storage Adjustment: ", sb.StorageRemainingAdjustment) 1743 c.staticLog.Println("Uptime Adjustment: ", sb.UptimeAdjustment) 1744 c.staticLog.Println("Version Adjustment: ", sb.VersionAdjustment) 1745 } 1746 1747 // Add this contract to the contractor and save. 1748 err = c.managedAcquireAndUpdateContractUtility(newContract.ID, skymodules.ContractUtility{ 1749 GoodForUpload: remainingGFU > 0, 1750 GoodForRefresh: true, 1751 GoodForRenew: true, 1752 }, false) 1753 if err != nil { 1754 c.staticLog.Println("Failed to update the contract utilities", err) 1755 return 1756 } 1757 remainingGFU-- 1758 1759 c.mu.Lock() 1760 err = c.save() 1761 c.mu.Unlock() 1762 if err != nil { 1763 c.staticLog.Println("Unable to save the contractor:", err) 1764 } 1765 } 1766 return 1767 } 1768 1769 // isLateRenewal returns true if a the contract is already a certain amount of 1770 // time into the renewal window and it hasn't been renewed yet due to a series 1771 // of failed renewals 1772 func isLateRenewal(allowance skymodules.Allowance, contract skymodules.RenterContract, blockHeight types.BlockHeight) bool { 1773 return blockHeight+(allowance.RenewWindow-allowance.RenewWindow/renewWindowLeewayDivisor) > contract.EndHeight 1774 }