gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/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/big" 10 "reflect" 11 12 "gitlab.com/NebulousLabs/errors" 13 "gitlab.com/NebulousLabs/fastrand" 14 15 "gitlab.com/SiaPrime/SiaPrime/build" 16 "gitlab.com/SiaPrime/SiaPrime/modules" 17 "gitlab.com/SiaPrime/SiaPrime/modules/renter/proto" 18 "gitlab.com/SiaPrime/SiaPrime/types" 19 ) 20 21 var ( 22 // ErrInsufficientAllowance indicates that the renter's allowance is less 23 // than the amount necessary to store at least one sector 24 ErrInsufficientAllowance = errors.New("allowance is not large enough to cover fees of contract creation") 25 errTooExpensive = errors.New("host price was too high") 26 ) 27 28 type ( 29 // fileContractRenewal is an instruction to renew a file contract. 30 fileContractRenewal struct { 31 id types.FileContractID 32 amount types.Currency 33 } 34 ) 35 36 // managedCheckForDuplicates checks for static contracts that have the same host 37 // key and moves the older one to old contracts. 38 func (c *Contractor) managedCheckForDuplicates() { 39 // Build map for comparison. 40 pubkeys := make(map[string]types.FileContractID) 41 var newContract, oldContract modules.RenterContract 42 for _, contract := range c.staticContracts.ViewAll() { 43 id, exists := pubkeys[contract.HostPublicKey.String()] 44 if !exists { 45 pubkeys[contract.HostPublicKey.String()] = contract.ID 46 continue 47 } 48 49 // Duplicate contract found, determine older contract to delete. 50 if rc, ok := c.staticContracts.View(id); ok { 51 if rc.StartHeight >= contract.StartHeight { 52 newContract, oldContract = rc, contract 53 } else { 54 newContract, oldContract = contract, rc 55 } 56 c.log.Printf("Duplicate contract found. New conract is %x and old contract is %v", newContract.ID, oldContract.ID) 57 58 // Get SafeContract 59 oldSC, ok := c.staticContracts.Acquire(oldContract.ID) 60 if !ok { 61 // Update map 62 pubkeys[contract.HostPublicKey.String()] = newContract.ID 63 continue 64 } 65 66 // Link the contracts to each other and then store the old contract 67 // in the record of historic contracts. 68 c.mu.Lock() 69 c.renewedFrom[newContract.ID] = oldContract.ID 70 c.renewedTo[oldContract.ID] = newContract.ID 71 c.oldContracts[oldContract.ID] = oldSC.Metadata() 72 c.pubKeysToContractID[string(newContract.HostPublicKey.Key)] = newContract.ID 73 74 // Save the contractor and delete the contract. 75 // 76 // TODO: Ideally these two things would happen atomically, but I'm 77 // not completely certain that's feasible with our current 78 // architecture. 79 err := c.save() 80 if err != nil { 81 c.log.Println("Failed to save the contractor after updating renewed maps.") 82 } 83 c.mu.Unlock() 84 c.staticContracts.Delete(oldSC) 85 86 // Update the pubkeys map to contain the newest contract id. 87 // 88 // TODO: This means that if there are multiple duplicates, say 3 89 // contracts that all share the same host, then the ordering may not 90 // be perfect. If in reality the renewal order was A<->B<->C, it's 91 // possible for the contractor to end up with A->C and B<->C in the 92 // mapping. 93 pubkeys[contract.HostPublicKey.String()] = newContract.ID 94 } 95 } 96 } 97 98 // managedEstimateRenewFundingRequirements estimates the amount of money that a 99 // contract is going to need in the next billing cycle by looking at how much 100 // storage is in the contract and what the historic usage pattern of the 101 // contract has been. 102 func (c *Contractor) managedEstimateRenewFundingRequirements(contract modules.RenterContract, blockHeight types.BlockHeight, allowance modules.Allowance) (types.Currency, error) { 103 // Fetch the host pricing to use in the estimate. 104 host, exists := c.hdb.Host(contract.HostPublicKey) 105 if !exists { 106 return types.ZeroCurrency, errors.New("could not find host in hostdb") 107 } 108 if host.Filtered { 109 return types.ZeroCurrency, errors.New("host is blacklisted") 110 } 111 112 // Estimate the amount of money that's going to be needed for existing 113 // storage. 114 dataStored := contract.Transaction.FileContractRevisions[0].NewFileSize 115 maintenanceCost := types.NewCurrency64(dataStored).Mul64(uint64(allowance.Period)).Mul(host.StoragePrice) 116 117 // For the upload and download estimates, we're going to need to know the 118 // amount of money that was spent on upload and download by this contract 119 // line in this period. That's going to require iterating over the renew 120 // history of the contract to get all the spending across any refreshes that 121 // occurred this period. 122 prevUploadSpending := contract.UploadSpending 123 prevDownloadSpending := contract.DownloadSpending 124 c.mu.Lock() 125 currentID := contract.ID 126 for i := 0; i < 10e3; i++ { // prevent an infinite loop if there's an [impossible] contract cycle 127 // If there is no previous contract, nothing to do. 128 var exists bool 129 currentID, exists = c.renewedFrom[currentID] 130 if !exists { 131 break 132 } 133 134 // If the contract is not in oldContracts, that's probably a bug, but 135 // nothing to do otherwise. 136 currentContract, exists := c.oldContracts[currentID] 137 if !exists { 138 c.log.Println("WARN: A known previous contract is not found in c.oldContracts") 139 break 140 } 141 142 // If the contract did not start in the current period, then it is not 143 // relevant, and none of the previous contracts will be relevant either. 144 if currentContract.StartHeight < c.currentPeriod { 145 break 146 } 147 148 // Add the upload and download spending. 149 prevUploadSpending = prevUploadSpending.Add(currentContract.UploadSpending) 150 prevDownloadSpending = prevDownloadSpending.Add(currentContract.DownloadSpending) 151 } 152 c.mu.Unlock() 153 154 // Estimate the amount of money that's going to be needed for new storage 155 // based on the amount of new storage added in the previous period. Account 156 // for both the storage price as well as the upload price. 157 prevUploadDataEstimate := prevUploadSpending 158 if !host.UploadBandwidthPrice.IsZero() { 159 // TODO: Because the host upload bandwidth price can change, this is not 160 // the best way to estimate the amount of data that was uploaded to this 161 // contract. Better would be to look at the amount of data stored in the 162 // contract from the previous cycle and use that to determine how much 163 // total data. 164 prevUploadDataEstimate = prevUploadDataEstimate.Div(host.UploadBandwidthPrice) 165 } 166 // Sanity check - the host may have changed prices, make sure we aren't 167 // assuming an unreasonable amount of data. 168 if types.NewCurrency64(dataStored).Cmp(prevUploadDataEstimate) < 0 { 169 prevUploadDataEstimate = types.NewCurrency64(dataStored) 170 } 171 // The estimated cost for new upload spending is the previous upload 172 // bandwidth plus the implied storage cost for all of the new data. 173 newUploadsCost := prevUploadSpending.Add(prevUploadDataEstimate.Mul64(uint64(allowance.Period)).Mul(host.StoragePrice)) 174 175 // The download cost is assumed to be the same. Even if the user is 176 // uploading more data, the expectation is that the download amounts will be 177 // relatively constant. Add in the contract price as well. 178 newDownloadsCost := prevDownloadSpending 179 contractPrice := host.ContractPrice 180 181 // Aggregate all estimates so far to compute the estimated siafunds fees. 182 // The transaction fees are not included in the siafunds estimate because 183 // users are not charged siafund fees on money that doesn't go into the file 184 // contract (and the transaction fee goes to the miners, not the file 185 // contract). 186 beforeSiafundFeesEstimate := maintenanceCost.Add(newUploadsCost).Add(newDownloadsCost).Add(contractPrice) 187 afterSiafundFeesEstimate := types.Tax(blockHeight, beforeSiafundFeesEstimate).Add(beforeSiafundFeesEstimate) 188 189 // Get an estimate for how much money we will be charged before going into 190 // the transaction pool. 191 _, maxTxnFee := c.tpool.FeeEstimation() 192 txnFees := maxTxnFee.Mul64(modules.EstimatedFileContractTransactionSetSize) 193 194 // Add them all up and then return the estimate plus 33% for error margin 195 // and just general volatility of usage pattern. 196 estimatedCost := afterSiafundFeesEstimate.Add(txnFees) 197 estimatedCost = estimatedCost.Add(estimatedCost.Div64(3)) 198 199 // Check for a sane minimum. The contractor should not be forming contracts 200 // with less than 'fileContractMinimumFunding / (num contracts)' of the 201 // value of the allowance. 202 minimum := allowance.Funds.MulFloat(fileContractMinimumFunding).Div64(allowance.Hosts) 203 if estimatedCost.Cmp(minimum) < 0 { 204 estimatedCost = minimum 205 } 206 return estimatedCost, nil 207 } 208 209 // managedInterruptContractMaintenance will issue an interrupt signal to any 210 // running maintenance, stopping that maintenance. If there are multiple threads 211 // running maintenance, they will all be stopped. 212 func (c *Contractor) managedInterruptContractMaintenance() { 213 // Spin up a thread to grab the maintenance lock. Signal that the lock was 214 // acquired after the lock is acquired. 215 gotLock := make(chan struct{}) 216 go func() { 217 c.maintenanceLock.Lock() 218 close(gotLock) 219 c.maintenanceLock.Unlock() 220 }() 221 222 // There may be multiple threads contending for the maintenance lock. Issue 223 // interrupts repeatedly until we get a signal that the maintenance lock has 224 // been acquired. 225 for { 226 select { 227 case <-gotLock: 228 return 229 case c.interruptMaintenance <- struct{}{}: 230 c.log.Debugln("Signal sent to interrupt contract maintenance") 231 } 232 } 233 } 234 235 // managedMarkContractsUtility checks every active contract in the contractor and 236 // figures out whether the contract is useful for uploading, and whether the 237 // contract should be renewed. 238 func (c *Contractor) managedMarkContractsUtility() error { 239 // Pull a new set of hosts from the hostdb that could be used as a new set 240 // to match the allowance. The lowest scoring host of these new hosts will 241 // be used as a baseline for determining whether our existing contracts are 242 // worthwhile. 243 c.mu.RLock() 244 hostCount := int(c.allowance.Hosts) 245 period := c.allowance.Period 246 height := c.blockHeight 247 c.mu.RUnlock() 248 // TODO Check for Hosts Subnet violation 249 // hosts, err := c.hdb.RandomHosts(hostCount+randomHostsBufferForScore, nil, nil, false) 250 hosts, err := c.hdb.RandomHosts(hostCount+randomHostsBufferForScore, nil, nil) 251 if err != nil { 252 return err 253 } 254 255 // Find the minimum score that a host is allowed to have to be considered 256 // good for upload. 257 var minScoreGFR types.Currency 258 var minScoreGFU types.Currency 259 if len(hosts) > 0 { 260 sb, err := c.hdb.ScoreBreakdown(hosts[0]) 261 if err != nil { 262 return err 263 } 264 lowestScore := sb.Score 265 for i := 1; i < len(hosts); i++ { 266 score, err := c.hdb.ScoreBreakdown(hosts[i]) 267 if err != nil { 268 return err 269 } 270 if score.Score.Cmp(lowestScore) < 0 { 271 lowestScore = score.Score 272 } 273 } 274 // Set the minimum acceptable score to a factor of the lowest score. 275 minScoreGFR = lowestScore.Div(scoreLeewayGoodForRenew) 276 minScoreGFU = lowestScore.Div(scoreLeewayGoodForUpload) 277 } 278 279 // Update utility fields for each contract. 280 for _, contract := range c.staticContracts.ViewAll() { 281 utility, err := func() (u modules.ContractUtility, err error) { 282 u = contract.Utility 283 284 // If the utility is locked, do nothing. 285 if u.Locked { 286 return u, nil 287 } 288 289 // Start the contract in good standing if the utility isn't locked 290 // but don't completely ignore the utility. A locked utility can 291 // always get worse but not better. 292 // TODO recheck if this is the right way. 293 u.GoodForUpload = true 294 u.GoodForRenew = true 295 296 host, exists := c.hdb.Host(contract.HostPublicKey) 297 // Contract has no utility if the host is not in the database. Or is 298 // filtered by the blacklist or whitelist. 299 if !exists || host.Filtered { 300 // Log if the utility has changed. 301 if u.GoodForUpload || u.GoodForRenew { 302 c.log.Printf("Marking contract as having no utility because found in hostDB: %v, or host is Filtered: %v - %v", exists, host.Filtered, contract.ID) 303 } 304 u.GoodForUpload = false 305 u.GoodForRenew = false 306 return u, nil 307 } 308 309 // Contract has no utility if the score is poor. 310 sb, err := c.hdb.ScoreBreakdown(host) 311 if err != nil { 312 return u, err 313 } 314 if !minScoreGFR.IsZero() && sb.Score.Cmp(minScoreGFR) < 0 { 315 // Log if the utility has changed. 316 if u.GoodForUpload || u.GoodForRenew { 317 c.log.Printf("Marking contract as having no utility because of host score: %v", contract.ID) 318 c.log.Println("Min Score:", minScoreGFR) 319 c.log.Println("Score: ", sb.Score) 320 c.log.Println("Age Adjustment: ", sb.AgeAdjustment) 321 c.log.Println("Burn Adjustment: ", sb.BurnAdjustment) 322 c.log.Println("Collateral Adjustment: ", sb.CollateralAdjustment) 323 c.log.Println("Duration Adjustment: ", sb.DurationAdjustment) 324 c.log.Println("Interaction Adjustment:", sb.InteractionAdjustment) 325 c.log.Println("Price Adjustment: ", sb.PriceAdjustment) 326 c.log.Println("Storage Adjustment: ", sb.StorageRemainingAdjustment) 327 c.log.Println("Uptime Adjustment: ", sb.UptimeAdjustment) 328 c.log.Println("Version Adjustment: ", sb.VersionAdjustment) 329 } 330 u.GoodForUpload = false 331 u.GoodForRenew = false 332 return u, nil 333 } 334 335 // Contract has no utility if the host is offline. 336 if isOffline(host) { 337 // Log if the utility has changed. 338 if u.GoodForUpload || u.GoodForRenew { 339 c.log.Println("Marking contract as having no utility because of host being offline", contract.ID) 340 } 341 u.GoodForUpload = false 342 u.GoodForRenew = false 343 return u, nil 344 } 345 346 // Contract should not be used for uplodaing if the score is poor. 347 if !minScoreGFU.IsZero() && sb.Score.Cmp(minScoreGFU) < 0 { 348 if u.GoodForUpload { 349 c.log.Println("Marking contract as not good for upload because of a poor score", contract.ID) 350 c.log.Println("Min Score:", minScoreGFU) 351 c.log.Println("Score: ", sb.Score) 352 c.log.Println("Age Adjustment: ", sb.AgeAdjustment) 353 c.log.Println("Burn Adjustment: ", sb.BurnAdjustment) 354 c.log.Println("Collateral Adjustment: ", sb.CollateralAdjustment) 355 c.log.Println("Duration Adjustment: ", sb.DurationAdjustment) 356 c.log.Println("Interaction Adjustment:", sb.InteractionAdjustment) 357 c.log.Println("Price Adjustment: ", sb.PriceAdjustment) 358 c.log.Println("Storage Adjustment: ", sb.StorageRemainingAdjustment) 359 c.log.Println("Uptime Adjustment: ", sb.UptimeAdjustment) 360 c.log.Println("Version Adjustment: ", sb.VersionAdjustment) 361 } 362 if !u.GoodForRenew { 363 c.log.Println("Marking contract as being good for renew", contract.ID) 364 } 365 u.GoodForUpload = false 366 u.GoodForRenew = true 367 return u, nil 368 } 369 370 // Contract should not be used for uploading if the time has come to 371 // renew the contract. 372 c.mu.RLock() 373 blockHeight := c.blockHeight 374 renewWindow := c.allowance.RenewWindow 375 c.mu.RUnlock() 376 if blockHeight+renewWindow >= contract.EndHeight { 377 if u.GoodForUpload { 378 c.log.Println("Marking contract as not good for upload because it is time to renew the contract", contract.ID) 379 } 380 if !u.GoodForRenew { 381 c.log.Println("Marking contract as being good for renew:", contract.ID) 382 } 383 u.GoodForUpload = false 384 u.GoodForRenew = true 385 return u, nil 386 } 387 388 // Contract should not be used for uploading if the contract does 389 // not have enough money remaining to perform the upload. 390 blockBytes := types.NewCurrency64(modules.SectorSize * uint64(period)) 391 sectorStoragePrice := host.StoragePrice.Mul(blockBytes) 392 sectorUploadBandwidthPrice := host.UploadBandwidthPrice.Mul64(modules.SectorSize) 393 sectorDownloadBandwidthPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize) 394 sectorBandwidthPrice := sectorUploadBandwidthPrice.Add(sectorDownloadBandwidthPrice) 395 sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice) 396 percentRemaining, _ := big.NewRat(0, 1).SetFrac(contract.RenterFunds.Big(), contract.TotalCost.Big()).Float64() 397 if contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0 || percentRemaining < MinContractFundUploadThreshold { 398 if u.GoodForUpload { 399 c.log.Printf("Marking contract as not good for upload because of insufficient funds: %v vs. %v - %v", contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0, percentRemaining, contract.ID) 400 } 401 if !u.GoodForRenew { 402 c.log.Println("Marking contract as being good for renew:", contract.ID) 403 } 404 u.GoodForUpload = false 405 u.GoodForRenew = true 406 return u, nil 407 } 408 409 // Contract should not be used for uploading if the host is out of storage. 410 if height-u.LastOOSErr <= oosRetryInterval { 411 if u.GoodForUpload { 412 c.log.Println("Marking contract as not being good for upload due to the host running out of storage:", contract.ID) 413 } 414 if !u.GoodForRenew { 415 c.log.Println("Marking contract as being good for renew:", contract.ID) 416 } 417 u.GoodForUpload = false 418 u.GoodForRenew = true 419 return u, nil 420 } 421 422 if !u.GoodForUpload || !u.GoodForRenew { 423 c.log.Println("Marking contract as being both GoodForUpload and GoodForRenew", u.GoodForUpload, u.GoodForRenew, contract.ID) 424 } 425 u.GoodForUpload = true 426 u.GoodForRenew = true 427 return u, nil 428 }() 429 if err != nil { 430 return err 431 } 432 433 // Apply changes. 434 err = c.managedUpdateContractUtility(contract.ID, utility) 435 if err != nil { 436 return err 437 } 438 } 439 return nil 440 } 441 442 // managedNewContract negotiates an initial file contract with the specified 443 // host, saves it, and returns it. 444 func (c *Contractor) managedNewContract(host modules.HostDBEntry, contractFunding types.Currency, endHeight types.BlockHeight) (types.Currency, modules.RenterContract, error) { 445 // reject hosts that are too expensive 446 if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 447 return types.ZeroCurrency, modules.RenterContract{}, errTooExpensive 448 } 449 // Determine if host settings align with allowance period 450 c.mu.Lock() 451 if reflect.DeepEqual(c.allowance, modules.Allowance{}) { 452 c.mu.Unlock() 453 return types.ZeroCurrency, modules.RenterContract{}, errors.New("called managedNewContract but allowance wasn't set") 454 } 455 period := c.allowance.Period 456 c.mu.Unlock() 457 458 if host.MaxDuration < period { 459 err := errors.New("unable to form contract with host due to insufficient MaxDuration of host") 460 return types.ZeroCurrency, modules.RenterContract{}, err 461 } 462 // cap host.MaxCollateral 463 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 464 host.MaxCollateral = maxCollateral 465 } 466 467 // get an address to use for negotiation 468 uc, err := c.wallet.NextAddress() 469 if err != nil { 470 return types.ZeroCurrency, modules.RenterContract{}, err 471 } 472 473 // get the wallet seed. 474 seed, _, err := c.wallet.PrimarySeed() 475 if err != nil { 476 return types.ZeroCurrency, modules.RenterContract{}, err 477 } 478 // derive the renter seed and wipe it once we are done with it. 479 renterSeed := proto.DeriveRenterSeed(seed) 480 defer fastrand.Read(renterSeed[:]) 481 482 // create contract params 483 c.mu.RLock() 484 params := proto.ContractParams{ 485 Allowance: c.allowance, 486 Host: host, 487 Funding: contractFunding, 488 StartHeight: c.blockHeight, 489 EndHeight: endHeight, 490 RefundAddress: uc.UnlockHash(), 491 RenterSeed: renterSeed.EphemeralRenterSeed(endHeight), 492 } 493 c.mu.RUnlock() 494 495 // wipe the renter seed once we are done using it. 496 defer fastrand.Read(params.RenterSeed[:]) 497 498 // create transaction builder and trigger contract formation. 499 txnBuilder, err := c.wallet.StartTransaction() 500 if err != nil { 501 return types.ZeroCurrency, modules.RenterContract{}, err 502 } 503 contract, err := c.staticContracts.FormContract(params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan()) 504 if err != nil { 505 txnBuilder.Drop() 506 return types.ZeroCurrency, modules.RenterContract{}, err 507 } 508 509 // Add a mapping from the contract's id to the public key of the host. 510 c.mu.Lock() 511 _, exists := c.pubKeysToContractID[contract.HostPublicKey.String()] 512 if exists { 513 c.mu.Unlock() 514 txnBuilder.Drop() 515 // We need to return a funding value because money was spent on this 516 // host, even though the full process could not be completed. 517 c.log.Println("WARN: Attempted to form a new contract with a host that we already have a contrat with.") 518 return contractFunding, modules.RenterContract{}, fmt.Errorf("We already have a contract with host %v", contract.HostPublicKey) 519 } 520 c.pubKeysToContractID[contract.HostPublicKey.String()] = contract.ID 521 c.mu.Unlock() 522 523 contractValue := contract.RenterFunds 524 c.log.Printf("Formed contract %v with %v for %v", contract.ID, host.NetAddress, contractValue.HumanString()) 525 return contractFunding, contract, nil 526 } 527 528 // managedPrunePubkeyMap will delete any pubkeys in the pubKeysToContractID map 529 // that no longer map to an active contract. 530 func (c *Contractor) managedPrunePubkeyMap() { 531 allContracts := c.staticContracts.ViewAll() 532 pks := make(map[string]struct{}) 533 for _, c := range allContracts { 534 pks[c.HostPublicKey.String()] = struct{}{} 535 } 536 c.mu.Lock() 537 for pk := range c.pubKeysToContractID { 538 if _, exists := pks[pk]; !exists { 539 delete(c.pubKeysToContractID, pk) 540 } 541 } 542 c.mu.Unlock() 543 } 544 545 // managedPrunedRedundantAddressRange uses the hostdb to find hosts that 546 // violate the rules about address ranges and cancels them. 547 func (c *Contractor) managedPrunedRedundantAddressRange() { 548 // Get all contracts which are not canceled. 549 allContracts := c.staticContracts.ViewAll() 550 var contracts []modules.RenterContract 551 for _, contract := range allContracts { 552 if contract.Utility.Locked && !contract.Utility.GoodForRenew && !contract.Utility.GoodForUpload { 553 // contract is canceled 554 continue 555 } 556 contracts = append(contracts, contract) 557 } 558 559 // Get all the public keys and map them to contract ids. 560 pks := make([]types.SiaPublicKey, 0, len(allContracts)) 561 cids := make(map[string]types.FileContractID) 562 for _, contract := range contracts { 563 pks = append(pks, contract.HostPublicKey) 564 cids[contract.HostPublicKey.String()] = contract.ID 565 } 566 567 // Let the hostdb filter out bad hosts and cancel contracts with those 568 // hosts. 569 badHosts := c.hdb.CheckForIPViolations(pks) 570 for _, host := range badHosts { 571 if err := c.managedCancelContract(cids[host.String()]); err != nil { 572 c.log.Print("WARNING: Wasn't able to cancel contract in managedPrunedRedundantAddressRange", err) 573 } 574 } 575 } 576 577 // managedRenew negotiates a new contract for data already stored with a host. 578 // It returns the new contract. This is a blocking call that performs network 579 // I/O. 580 func (c *Contractor) managedRenew(sc *proto.SafeContract, contractFunding types.Currency, newEndHeight types.BlockHeight) (modules.RenterContract, error) { 581 // For convenience 582 contract := sc.Metadata() 583 // Sanity check - should not be renewing a bad contract. 584 utility, ok := c.managedContractUtility(contract.ID) 585 if !ok || !utility.GoodForRenew { 586 c.log.Critical(fmt.Sprintf("Renewing a contract that has been marked as !GoodForRenew %v/%v", 587 ok, utility.GoodForRenew)) 588 } 589 590 // Fetch the host associated with this contract. 591 host, ok := c.hdb.Host(contract.HostPublicKey) 592 c.mu.Lock() 593 if reflect.DeepEqual(c.allowance, modules.Allowance{}) { 594 c.mu.Unlock() 595 return modules.RenterContract{}, errors.New("called managedRenew but allowance isn't set") 596 } 597 period := c.allowance.Period 598 c.mu.Unlock() 599 if !ok { 600 return modules.RenterContract{}, errors.New("no record of that host") 601 } else if host.Filtered { 602 return modules.RenterContract{}, errors.New("host is blacklisted") 603 } else if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 604 return modules.RenterContract{}, errTooExpensive 605 } else if host.MaxDuration < period { 606 return modules.RenterContract{}, errors.New("insufficient MaxDuration of host") 607 } 608 609 // cap host.MaxCollateral 610 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 611 host.MaxCollateral = maxCollateral 612 } 613 614 // get an address to use for negotiation 615 uc, err := c.wallet.NextAddress() 616 if err != nil { 617 return modules.RenterContract{}, err 618 } 619 620 // get the wallet seed 621 seed, _, err := c.wallet.PrimarySeed() 622 if err != nil { 623 return modules.RenterContract{}, err 624 } 625 // derive the renter seed and wipe it after we are done with it. 626 renterSeed := proto.DeriveRenterSeed(seed) 627 defer fastrand.Read(renterSeed[:]) 628 629 // create contract params 630 c.mu.RLock() 631 params := proto.ContractParams{ 632 Allowance: c.allowance, 633 Host: host, 634 Funding: contractFunding, 635 StartHeight: c.blockHeight, 636 EndHeight: newEndHeight, 637 RefundAddress: uc.UnlockHash(), 638 RenterSeed: renterSeed.EphemeralRenterSeed(newEndHeight), 639 } 640 c.mu.RUnlock() 641 642 // wipe the renter seed once we are done using it. 643 defer fastrand.Read(params.RenterSeed[:]) 644 645 // execute negotiation protocol 646 txnBuilder, err := c.wallet.StartTransaction() 647 if err != nil { 648 return modules.RenterContract{}, err 649 } 650 newContract, err := c.staticContracts.Renew(sc, params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan()) 651 if err != nil { 652 txnBuilder.Drop() // return unused outputs to wallet 653 return modules.RenterContract{}, err 654 } 655 656 // Add a mapping from the contract's id to the public key of the host. This 657 // will destroy the previous mapping from pubKey to contract id but other 658 // modules are only interested in the most recent contract anyway. 659 c.mu.Lock() 660 c.pubKeysToContractID[newContract.HostPublicKey.String()] = newContract.ID 661 c.mu.Unlock() 662 663 return newContract, nil 664 } 665 666 // managedRenewContract will use the renew instructions to renew a contract, 667 // returning the amount of money that was put into the contract for renewal. 668 func (c *Contractor) managedRenewContract(renewInstructions fileContractRenewal, currentPeriod types.BlockHeight, allowance modules.Allowance, blockHeight, endHeight types.BlockHeight) (fundsSpent types.Currency, err error) { 669 // Pull the variables out of the renewal. 670 id := renewInstructions.id 671 amount := renewInstructions.amount 672 673 // Mark the contract as being renewed, and defer logic to unmark it 674 // once renewing is complete. 675 c.log.Debugln("Marking a contract for renew:", id) 676 c.mu.Lock() 677 c.renewing[id] = true 678 c.mu.Unlock() 679 defer func() { 680 c.log.Debugln("Unmarking the contract for renew", id) 681 c.mu.Lock() 682 delete(c.renewing, id) 683 c.mu.Unlock() 684 }() 685 686 // Wait for any active editors/downloaders/sessions to finish for this 687 // contract, and then grab the latest revision. 688 c.mu.RLock() 689 e, eok := c.editors[id] 690 d, dok := c.downloaders[id] 691 s, sok := c.sessions[id] 692 c.mu.RUnlock() 693 if eok { 694 c.log.Debugln("Waiting for editor invalidation") 695 e.invalidate() 696 c.log.Debugln("Got editor invalidation") 697 } 698 if dok { 699 c.log.Debugln("Waiting for downloader invalidation") 700 d.invalidate() 701 c.log.Debugln("Got downloader invalidation") 702 } 703 if sok { 704 c.log.Debugln("Waiting for session invalidation") 705 s.invalidate() 706 c.log.Debugln("Got session invalidation") 707 } 708 709 // Fetch the contract that we are renewing. 710 c.log.Debugln("Acquiring contract from the contract set", id) 711 oldContract, exists := c.staticContracts.Acquire(id) 712 if !exists { 713 c.log.Debugln("Contract does not seem to exist") 714 return types.ZeroCurrency, errors.New("contract no longer exists") 715 } 716 // Return the contract if it's not useful for renewing. 717 oldUtility, ok := c.managedContractUtility(id) 718 if !ok || !oldUtility.GoodForRenew { 719 c.log.Printf("Contract %v slated for renew is marked not good for renew: %v /%v", 720 id, ok, oldUtility.GoodForRenew) 721 c.staticContracts.Return(oldContract) 722 return types.ZeroCurrency, errors.New("contract is marked not good for renew") 723 } 724 725 // Perform the actual renew. If the renew fails, return the 726 // contract. If the renew fails we check how often it has failed 727 // before. Once it has failed for a certain number of blocks in a 728 // row and reached its second half of the renew window, we give up 729 // on renewing it and set goodForRenew to false. 730 c.log.Debugln("calling managedRenew on contract", id) 731 newContract, errRenew := c.managedRenew(oldContract, amount, endHeight) 732 c.log.Debugln("managedRenew has returned with error:", errRenew) 733 if errRenew != nil { 734 // Increment the number of failed renews for the contract if it 735 // was the host's fault. 736 if modules.IsHostsFault(errRenew) { 737 c.mu.Lock() 738 c.numFailedRenews[oldContract.Metadata().ID]++ 739 totalFailures := c.numFailedRenews[oldContract.Metadata().ID] 740 c.mu.Unlock() 741 c.log.Debugln("remote host determined to be at fault, tallying up failed renews", totalFailures, id) 742 } 743 744 // Check if contract has to be replaced. 745 md := oldContract.Metadata() 746 c.mu.RLock() 747 numRenews, failedBefore := c.numFailedRenews[md.ID] 748 c.mu.RUnlock() 749 secondHalfOfWindow := blockHeight+allowance.RenewWindow/2 >= md.EndHeight 750 replace := numRenews >= consecutiveRenewalsBeforeReplacement 751 if failedBefore && secondHalfOfWindow && replace { 752 oldUtility.GoodForRenew = false 753 oldUtility.GoodForUpload = false 754 oldUtility.Locked = true 755 err := oldContract.UpdateUtility(oldUtility) 756 if err != nil { 757 c.log.Println("WARN: failed to mark contract as !goodForRenew:", err) 758 } 759 c.log.Printf("WARN: consistently failed to renew %v, marked as bad and locked: %v\n", 760 oldContract.Metadata().HostPublicKey, errRenew) 761 c.staticContracts.Return(oldContract) 762 return types.ZeroCurrency, errors.AddContext(errRenew, "contract marked as bad for too many consecutive failed renew attempts") 763 } 764 765 // Seems like it doesn't have to be replaced yet. Log the 766 // failure and number of renews that have failed so far. 767 c.log.Printf("WARN: failed to renew contract %v [%v]: %v\n", 768 oldContract.Metadata().HostPublicKey, numRenews, errRenew) 769 c.staticContracts.Return(oldContract) 770 return types.ZeroCurrency, errors.AddContext(errRenew, "contract renewal with host was unsuccessful") 771 } 772 c.log.Printf("Renewed contract %v\n", id) 773 774 // Update the utility values for the new contract, and for the old 775 // contract. 776 newUtility := modules.ContractUtility{ 777 GoodForUpload: true, 778 GoodForRenew: true, 779 } 780 if err := c.managedUpdateContractUtility(newContract.ID, newUtility); err != nil { 781 c.log.Println("Failed to update the contract utilities", err) 782 c.staticContracts.Return(oldContract) 783 return amount, nil // Error is not returned because the renew succeeded. 784 } 785 oldUtility.GoodForRenew = false 786 oldUtility.GoodForUpload = false 787 oldUtility.Locked = true 788 if err := oldContract.UpdateUtility(oldUtility); err != nil { 789 c.log.Println("Failed to update the contract utilities", err) 790 c.staticContracts.Return(oldContract) 791 return amount, nil // Error is not returned because the renew succeeded. 792 } 793 794 if c.staticDeps.Disrupt("InterruptContractSaveToDiskAfterDeletion") { 795 c.staticContracts.Return(oldContract) 796 return amount, errors.New("InterruptContractSaveToDiskAfterDeletion disrupt") 797 } 798 // Lock the contractor as we update it to use the new contract 799 // instead of the old contract. 800 c.mu.Lock() 801 // Link Contracts 802 c.renewedFrom[newContract.ID] = id 803 c.renewedTo[id] = newContract.ID 804 // Store the contract in the record of historic contracts. 805 c.oldContracts[id] = oldContract.Metadata() 806 // Save the contractor. 807 err = c.save() 808 if err != nil { 809 c.log.Println("Failed to save the contractor after creating a new contract.") 810 } 811 c.mu.Unlock() 812 // Delete the old contract. 813 c.staticContracts.Delete(oldContract) 814 return amount, nil 815 } 816 817 // managedFindRecoverableContracts will spawn a thread to rescan parts of the 818 // blockchain for recoverable contracts if the wallet has been locked during the 819 // last scan. 820 func (c *Contractor) managedFindRecoverableContracts() { 821 if c.staticDeps.Disrupt("disableAutomaticContractRecoveryScan") { 822 return 823 } 824 c.mu.RLock() 825 cc := c.recentRecoveryChange 826 c.mu.RUnlock() 827 if err := c.managedInitRecoveryScan(cc); err != nil { 828 c.log.Debug(err) 829 return 830 } 831 } 832 833 // managedUpdateContractUtility is a helper function that acquires a contract, updates 834 // its ContractUtility and returns the contract again. 835 func (c *Contractor) managedUpdateContractUtility(id types.FileContractID, utility modules.ContractUtility) error { 836 safeContract, ok := c.staticContracts.Acquire(id) 837 if !ok { 838 return errors.New("failed to acquire contract for update") 839 } 840 defer c.staticContracts.Return(safeContract) 841 return safeContract.UpdateUtility(utility) 842 } 843 844 // threadedContractMaintenance checks the set of contracts that the contractor 845 // has against the allownace, renewing any contracts that need to be renewed, 846 // dropping contracts which are no longer worthwhile, and adding contracts if 847 // there are not enough. 848 // 849 // Between each network call, the thread checks whether a maintenance interrupt 850 // signal is being sent. If so, maintenance returns, yielding to whatever thread 851 // issued the interrupt. 852 func (c *Contractor) threadedContractMaintenance() { 853 err := c.tg.Add() 854 if err != nil { 855 return 856 } 857 defer c.tg.Done() 858 859 // Only one instance of this thread should be running at a time. Under 860 // normal conditions, fine to return early if another thread is already 861 // doing maintenance. The next block will trigger another round. Under 862 // testing, control is insufficient if the maintenance loop isn't guaranteed 863 // to run. 864 if build.Release == "testing" { 865 c.maintenanceLock.Lock() 866 } else if !c.maintenanceLock.TryLock() { 867 c.log.Debugln("maintenance lock could not be obtained") 868 return 869 } 870 defer c.maintenanceLock.Unlock() 871 872 // Perform general cleanup of the contracts. This includes recovering lost 873 // contracts, archiving contracts, and other cleanup work. This should all 874 // happen before the rest of the maintenance. 875 c.managedFindRecoverableContracts() 876 c.managedRecoverContracts() 877 c.managedArchiveContracts() 878 c.managedCheckForDuplicates() 879 c.managedPrunePubkeyMap() 880 881 //Save the work if CheckForIPViolation set to false 882 if c.hdb.IPViolationsCheck() { 883 c.managedPrunedRedundantAddressRange() 884 } 885 886 err = c.managedMarkContractsUtility() 887 if err != nil { 888 c.log.Debugln("Unable to mark contract utilities:", err) 889 return 890 } 891 err = c.hdb.UpdateContracts(c.staticContracts.ViewAll()) 892 if err != nil { 893 c.log.Debugln("Unable to update hostdb contracts:", err) 894 return 895 } 896 897 // If there are no hosts requested by the allowance, there is no remaining 898 // work. 899 c.mu.RLock() 900 wantedHosts := c.allowance.Hosts 901 c.mu.RUnlock() 902 if wantedHosts <= 0 { 903 c.log.Debugln("Exiting contract maintenance because the number of desired hosts is <= zero.") 904 return 905 } 906 907 // The rest of this function needs to know a few of the stateful variables 908 // from the contractor, build those up under a lock so that the rest of the 909 // function can execute without lock contention. 910 c.mu.Lock() 911 allowance := c.allowance 912 blockHeight := c.blockHeight 913 currentPeriod := c.currentPeriod 914 endHeight := c.contractEndHeight() 915 c.mu.Unlock() 916 917 // Create the renewSet and refreshSet. Each is a list of contracts that need 918 // to be renewed, paired with the amount of money to use in each renewal. 919 // 920 // The renewSet is specifically contracts which are being renewed because 921 // they are about to expire. And the refreshSet is contracts that are being 922 // renewed because they are out of money. 923 // 924 // The contractor will prioritize contracts in the renewSet over contracts 925 // in the refreshSet. If the wallet does not have enough money, or if the 926 // allowance does not have enough money, the contractor will prefer to save 927 // data in the long term rather than renew a contract. 928 var renewSet []fileContractRenewal 929 var refreshSet []fileContractRenewal 930 931 // Iterate through the contracts again, figuring out which contracts to 932 // renew and how much extra funds to renew them with. 933 for _, contract := range c.staticContracts.ViewAll() { 934 c.log.Debugln("Examining a contract:", contract.HostPublicKey, contract.ID) 935 // Skip any host that does not match our whitelist/blacklist filter 936 // settings. 937 host, _ := c.hdb.Host(contract.HostPublicKey) 938 if host.Filtered { 939 c.log.Debugln("Contract skipped because it is filtered") 940 continue 941 } 942 943 // Skip any contracts which do not exist or are otherwise unworthy for 944 // renewal. 945 utility, ok := c.managedContractUtility(contract.ID) 946 if !ok || !utility.GoodForRenew { 947 if uint64(blockHeight-contract.StartHeight) < types.BlocksPerWeek { 948 c.log.Debugln("Contract did not last 1 week and is not being renewed", contract.ID) 949 } 950 c.log.Debugln("Contract skipped because it is not good for renew (utility.GoodForRenew, exists)", utility.GoodForRenew, ok) 951 continue 952 } 953 954 // If the contract needs to be renewed because it is about to expire, 955 // calculate a spending for the contract that is proportional to how 956 // much money was spend on the contract throughout this billing cycle 957 // (which is now ending). 958 if blockHeight+allowance.RenewWindow >= contract.EndHeight && !c.staticDeps.Disrupt("disableRenew") { 959 renewAmount, err := c.managedEstimateRenewFundingRequirements(contract, blockHeight, allowance) 960 if err != nil { 961 c.log.Debugln("Contract skipped because there was an error estimating renew funding requirements", renewAmount, err) 962 continue 963 } 964 renewSet = append(renewSet, fileContractRenewal{ 965 id: contract.ID, 966 amount: renewAmount, 967 }) 968 c.log.Debugln("Contract has been added to the renew set for being past the renew height") 969 continue 970 } 971 972 // Check if the contract is empty. We define a contract as being empty 973 // if less than 'minContractFundRenewalThreshold' funds are remaining 974 // (3% at time of writing), or if there is less than 3 sectors worth of 975 // storage+upload+download remaining. 976 blockBytes := types.NewCurrency64(modules.SectorSize * uint64(allowance.Period)) 977 sectorStoragePrice := host.StoragePrice.Mul(blockBytes) 978 sectorUploadBandwidthPrice := host.UploadBandwidthPrice.Mul64(modules.SectorSize) 979 sectorDownloadBandwidthPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize) 980 sectorBandwidthPrice := sectorUploadBandwidthPrice.Add(sectorDownloadBandwidthPrice) 981 sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice) 982 percentRemaining, _ := big.NewRat(0, 1).SetFrac(contract.RenterFunds.Big(), contract.TotalCost.Big()).Float64() 983 if contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0 || percentRemaining < MinContractFundRenewalThreshold && !c.staticDeps.Disrupt("disableRenew") { 984 // Renew the contract with double the amount of funds that the 985 // contract had previously. The reason that we double the funding 986 // instead of doing anything more clever is that we don't know what 987 // the usage pattern has been. The spending could have all occurred 988 // in one burst recently, and the user might need a contract that 989 // has substantially more money in it. 990 // 991 // We double so that heavily used contracts can grow in funding 992 // quickly without consuming too many transaction fees, however this 993 // does mean that a larger percentage of funds get locked away from 994 // the user in the event that the user stops uploading immediately 995 // after the renew. 996 refreshSet = append(refreshSet, fileContractRenewal{ 997 id: contract.ID, 998 amount: contract.TotalCost.Mul64(2), 999 }) 1000 c.log.Debugln("Contract identified as needing to be added to refresh set", contract.RenterFunds, sectorPrice.Mul64(3), percentRemaining, MinContractFundRenewalThreshold) 1001 } else { 1002 c.log.Debugln("Contract did not get added to the refresh set", contract.RenterFunds, sectorPrice.Mul64(3), percentRemaining, MinContractFundRenewalThreshold) 1003 } 1004 } 1005 if len(renewSet) != 0 || len(refreshSet) != 0 { 1006 c.log.Printf("renewing %v contracts and refreshing %v contracts", len(renewSet), len(refreshSet)) 1007 } 1008 1009 // Update the failed renew map so that it only contains contracts which we 1010 // are currently trying to renew or refresh. The failed renew map is a map 1011 // that we use to track how many times consecutively we failed to renew a 1012 // contract with a host, so that we know if we need to abandon that host. 1013 c.mu.Lock() 1014 newFirstFailedRenew := make(map[types.FileContractID]types.BlockHeight) 1015 for _, r := range renewSet { 1016 if _, exists := c.numFailedRenews[r.id]; exists { 1017 newFirstFailedRenew[r.id] = c.numFailedRenews[r.id] 1018 } 1019 } 1020 for _, r := range refreshSet { 1021 if _, exists := c.numFailedRenews[r.id]; exists { 1022 newFirstFailedRenew[r.id] = c.numFailedRenews[r.id] 1023 } 1024 } 1025 c.numFailedRenews = newFirstFailedRenew 1026 c.mu.Unlock() 1027 1028 // Depend on the PeriodSpending function to get a breakdown of spending in 1029 // the contractor. Then use that to determine how many funds remain 1030 // available in the allowance for renewals. 1031 spending := c.PeriodSpending() 1032 var fundsRemaining types.Currency 1033 // Check for an underflow. This can happen if the user reduced their 1034 // allowance at some point to less than what we've already spent. 1035 if spending.TotalAllocated.Cmp(allowance.Funds) < 0 { 1036 fundsRemaining = allowance.Funds.Sub(spending.TotalAllocated) 1037 } 1038 c.log.Debugln("The allowance has this many remaning funds:", fundsRemaining) 1039 1040 // Go through the contracts we've assembled for renewal. Any contracts that 1041 // need to be renewed because they are expiring (renewSet) get priority over 1042 // contracts that need to be renewed because they have exhausted their funds 1043 // (refreshSet). If there is not enough money available, the more expensive 1044 // contracts will be skipped. 1045 for _, renewal := range renewSet { 1046 unlocked, err := c.wallet.Unlocked() 1047 if !unlocked || err != nil { 1048 c.log.Println("contractor is attempting to renew contracts that are about to expire, however the wallet is locked") 1049 return 1050 } 1051 1052 c.log.Println("Attempting to perform a renewal:", renewal.id) 1053 // Skip this renewal if we don't have enough funds remaining. 1054 if renewal.amount.Cmp(fundsRemaining) > 0 { 1055 c.log.Debugln("Skipping renewal because there are not enough funds remaining in the allowance", renewal.id, renewal.amount, fundsRemaining) 1056 continue 1057 } 1058 1059 // Renew one contract. The error is ignored because the renew function 1060 // already will have logged the error, and in the event of an error, 1061 // 'fundsSpent' will return '0'. 1062 fundsSpent, err := c.managedRenewContract(renewal, currentPeriod, allowance, blockHeight, endHeight) 1063 if err != nil { 1064 c.log.Println("Error renewing a contract", renewal.id, err) 1065 } else { 1066 c.log.Println("Renewal completed without error") 1067 } 1068 fundsRemaining = fundsRemaining.Sub(fundsSpent) 1069 1070 // Return here if an interrupt or kill signal has been sent. 1071 select { 1072 case <-c.tg.StopChan(): 1073 c.log.Println("returning because the renter was stopped") 1074 return 1075 case <-c.interruptMaintenance: 1076 c.log.Println("returning because maintenance was interrupted") 1077 return 1078 default: 1079 } 1080 } 1081 for _, renewal := range refreshSet { 1082 1083 // Skip this renewal if we don't have enough funds remaining. 1084 c.log.Debugln("Attempting to perform a contract refresh:", renewal.id) 1085 if renewal.amount.Cmp(fundsRemaining) > 0 { 1086 c.log.Println("skipping refresh because there are not enough funds remaining in the allowance", renewal.amount, fundsRemaining) 1087 continue 1088 } 1089 1090 // Renew one contract. The error is ignored because the renew function 1091 // already will have logged the error, and in the event of an error, 1092 // 'fundsSpent' will return '0'. 1093 fundsSpent, err := c.managedRenewContract(renewal, currentPeriod, allowance, blockHeight, endHeight) 1094 if err != nil { 1095 c.log.Println("Error refreshing a contract", renewal.id, err) 1096 } 1097 fundsRemaining = fundsRemaining.Sub(fundsSpent) 1098 1099 // Return here if an interrupt or kill signal has been sent. 1100 select { 1101 case <-c.tg.StopChan(): 1102 c.log.Println("returning because the renter was stopped") 1103 return 1104 case <-c.interruptMaintenance: 1105 c.log.Println("returning because maintenance was interrupted") 1106 return 1107 default: 1108 } 1109 } 1110 1111 // Count the number of contracts which are good for uploading, and then make 1112 // more as needed to fill the gap. 1113 uploadContracts := 0 1114 for _, id := range c.staticContracts.IDs() { 1115 if cu, ok := c.managedContractUtility(id); ok && cu.GoodForUpload { 1116 uploadContracts++ 1117 } 1118 } 1119 c.mu.RLock() 1120 neededContracts := int(c.allowance.Hosts) - uploadContracts 1121 c.mu.RUnlock() 1122 if neededContracts <= 0 { 1123 c.log.Debugln("do not seem to need more contracts") 1124 return 1125 } 1126 c.log.Println("need more contracts:", neededContracts) 1127 1128 // Assemble two exclusion lists. The first one includes all hosts that we 1129 // already have contracts with and the second one includes all hosts we 1130 // have active contracts with. Then select a new batch of hosts to attempt 1131 // contract formation with. 1132 allContracts := c.staticContracts.ViewAll() 1133 c.mu.RLock() 1134 var blacklist []types.SiaPublicKey 1135 var addressBlacklist []types.SiaPublicKey 1136 1137 for _, contract := range allContracts { 1138 blacklist = append(blacklist, contract.HostPublicKey) 1139 if c.hdb.IPViolationsCheck() { 1140 //NOTE: FilterHostsSubnet 1141 if !contract.Utility.Locked || contract.Utility.GoodForRenew || contract.Utility.GoodForUpload { 1142 addressBlacklist = append(addressBlacklist, contract.HostPublicKey) 1143 } 1144 } 1145 } 1146 1147 // Add the hosts we have recoverable contracts with to the blacklist to 1148 // avoid losing existing data by forming a new/empty contract. 1149 for _, contract := range c.recoverableContracts { 1150 blacklist = append(blacklist, contract.HostPublicKey) 1151 } 1152 1153 initialContractFunds := c.allowance.Funds.Div64(c.allowance.Hosts).Div64(3) 1154 c.mu.RUnlock() 1155 hosts, err := c.hdb.RandomHosts(neededContracts*4+randomHostsBufferForScore, blacklist, addressBlacklist) 1156 if err != nil { 1157 c.log.Println("WARN: not forming new contracts:", err) 1158 return 1159 } 1160 c.log.Debugln("trying to form contracts with hosts, pulled this many hosts from hostdb:", len(hosts)) 1161 1162 // Form contracts with the hosts one at a time, until we have enough 1163 // contracts. 1164 for _, host := range hosts { 1165 unlocked, err := c.wallet.Unlocked() 1166 if !unlocked || err != nil { 1167 c.log.Println("contractor is attempting to establish new contracts with hosts, however the wallet is locked") 1168 return 1169 } 1170 1171 // Determine if we have enough money to form a new contract. 1172 if fundsRemaining.Cmp(initialContractFunds) < 0 { 1173 c.log.Println("WARN: need to form new contracts, but unable to because of a low allowance") 1174 break 1175 } 1176 1177 // If we are using a custom resolver we need to replace the domain name 1178 // with 127.0.0.1 to be able to form contracts. 1179 if c.staticDeps.Disrupt("customResolver") { 1180 port := host.NetAddress.Port() 1181 host.NetAddress = modules.NetAddress(fmt.Sprintf("127.0.0.1:%s", port)) 1182 } 1183 1184 // Attempt forming a contract with this host. 1185 fundsSpent, newContract, err := c.managedNewContract(host, initialContractFunds, endHeight) 1186 if err != nil { 1187 c.log.Printf("Attempted to form a contract with %v, but negotiation failed: %v\n", host.NetAddress, err) 1188 continue 1189 } 1190 fundsRemaining = fundsRemaining.Sub(fundsSpent) 1191 1192 sb, err := c.hdb.ScoreBreakdown(host) 1193 if err == nil { 1194 c.log.Println("A new contract has been formed with a host:", newContract.ID) 1195 c.log.Println("Score: ", sb.Score) 1196 c.log.Println("Age Adjustment: ", sb.AgeAdjustment) 1197 c.log.Println("Burn Adjustment: ", sb.BurnAdjustment) 1198 c.log.Println("Collateral Adjustment: ", sb.CollateralAdjustment) 1199 c.log.Println("Duration Adjustment: ", sb.DurationAdjustment) 1200 c.log.Println("Interaction Adjustment:", sb.InteractionAdjustment) 1201 c.log.Println("Price Adjustment: ", sb.PriceAdjustment) 1202 c.log.Println("Storage Adjustment: ", sb.StorageRemainingAdjustment) 1203 c.log.Println("Uptime Adjustment: ", sb.UptimeAdjustment) 1204 c.log.Println("Version Adjustment: ", sb.VersionAdjustment) 1205 } 1206 1207 // Add this contract to the contractor and save. 1208 err = c.managedUpdateContractUtility(newContract.ID, modules.ContractUtility{ 1209 GoodForUpload: true, 1210 GoodForRenew: true, 1211 }) 1212 if err != nil { 1213 c.log.Println("Failed to update the contract utilities", err) 1214 return 1215 } 1216 c.mu.Lock() 1217 err = c.save() 1218 c.mu.Unlock() 1219 if err != nil { 1220 c.log.Println("Unable to save the contractor:", err) 1221 } 1222 1223 // Quit the loop if we've replaced all needed contracts. 1224 neededContracts-- 1225 if neededContracts <= 0 { 1226 break 1227 } 1228 1229 // Soft sleep before making the next contract. 1230 select { 1231 case <-c.tg.StopChan(): 1232 return 1233 case <-c.interruptMaintenance: 1234 return 1235 default: 1236 } 1237 } 1238 }