github.com/nebulouslabs/sia@v1.3.7/modules/renter/contractor/contracts.go (about) 1 package contractor 2 3 // contracts.go handles forming and renewing contracts for the contractor. This 4 // includes deciding when new contracts need to be formed, when contracts need 5 // to be renewed, and if contracts need to be blacklisted. 6 7 import ( 8 "fmt" 9 "math/big" 10 11 "github.com/NebulousLabs/Sia/build" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/modules/renter/proto" 14 "github.com/NebulousLabs/Sia/types" 15 16 "github.com/NebulousLabs/errors" 17 ) 18 19 var ( 20 // ErrInsufficientAllowance indicates that the renter's allowance is less 21 // than the amount necessary to store at least one sector 22 ErrInsufficientAllowance = errors.New("allowance is not large enough to cover fees of contract creation") 23 errTooExpensive = errors.New("host price was too high") 24 ) 25 26 type ( 27 // fileContractRenewal is an instruction to renew a file contract. 28 fileContractRenewal struct { 29 id types.FileContractID 30 amount types.Currency 31 } 32 ) 33 34 // contractEndHeight returns the height at which the Contractor's contracts 35 // end. If there are no contracts, it returns zero. 36 func (c *Contractor) contractEndHeight() types.BlockHeight { 37 return c.currentPeriod + c.allowance.Period 38 } 39 40 // managedContractUtility returns the ContractUtility for a contract with a given id. 41 func (c *Contractor) managedContractUtility(id types.FileContractID) (modules.ContractUtility, bool) { 42 rc, exists := c.staticContracts.View(id) 43 if !exists { 44 return modules.ContractUtility{}, false 45 } 46 return rc.Utility, true 47 } 48 49 // managedEstimateRenewFundingRequirements estimates the amount of money that a 50 // contract is going to need in the next billing cycle by looking at how much 51 // storage is in the contract and what the historic usage pattern of the 52 // contract has been. 53 // 54 // TODO: This looks at the spending metrics of the contract being renewed, but 55 // if the contract itself is a refresh of a contract that ran out of funds, 56 // these estimates may be off because they do not include the spending from the 57 // same-period parent contracts of this contract. This estimator function needs 58 // to be extended to support adding up all the parent spending too. These 59 // spending estimates will apply to uploading and downloading, but not to 60 // storage or fees or contract price. 61 func (c *Contractor) managedEstimateRenewFundingRequirements(contract modules.RenterContract, blockHeight types.BlockHeight, allowance modules.Allowance) (types.Currency, error) { 62 // Fetch the host pricing to use in the estimate. 63 host, exists := c.hdb.Host(contract.HostPublicKey) 64 if !exists { 65 return types.ZeroCurrency, errors.New("could not find host in hostdb") 66 } 67 68 // Estimate the amount of money that's going to be needed for existing 69 // storage. 70 dataStored := contract.Transaction.FileContractRevisions[0].NewFileSize 71 maintenanceCost := types.NewCurrency64(dataStored).Mul64(uint64(allowance.Period)).Mul(host.StoragePrice) 72 73 // Estimate the amount of money that's going to be needed for new storage 74 // based on the amount of new storage added in the previous period. Account 75 // for both the storage price as well as the upload price. 76 // 77 // TODO: We are currently using a very crude method to estimate the amount 78 // of data uploaded, the host could have easily changed prices partway 79 // through the contract, which would cause this estimate to fail. 80 prevUploadSpending := contract.UploadSpending 81 prevUploadDataEstimate := contract.UploadSpending.Div(host.UploadBandwidthPrice) 82 // Sanity check - the host may have changed prices, make sure we aren't 83 // assuming an unreasonable amount of data. 84 if types.NewCurrency64(dataStored).Cmp(prevUploadDataEstimate) < 0 { 85 prevUploadDataEstimate = types.NewCurrency64(dataStored) 86 } 87 // The estimated cost for new upload spending is the previous upload 88 // bandwidth plus the implied storage cost for all of the new data. 89 newUploadsCost := prevUploadSpending.Add(prevUploadDataEstimate.Mul64(uint64(allowance.Period)).Mul(host.StoragePrice)) 90 91 // Estimate the amount of money that's going to be spent on downloads. 92 newDownloadsCost := contract.DownloadSpending 93 94 // We will also need to pay the host contract price. 95 contractPrice := host.ContractPrice 96 97 // Aggregate all estimates so far to compute the estimated siafunds fees. 98 // The transaction fees are not included in the siafunds estimate because 99 // users are not charged siafund fees on money that doesn't go into the file 100 // contract (and the transaction fee goes to the miners, not the file 101 // contract). 102 beforeSiafundFeesEstimate := maintenanceCost.Add(newUploadsCost).Add(newDownloadsCost).Add(contractPrice) 103 afterSiafundFeesEstimate := types.Tax(blockHeight, beforeSiafundFeesEstimate).Add(beforeSiafundFeesEstimate) 104 105 // Get an estimate for how much money we will be charged before going into 106 // the transaction pool. 107 _, maxTxnFee := c.tpool.FeeEstimation() 108 txnFees := maxTxnFee.Mul64(modules.EstimatedFileContractTransactionSetSize) 109 110 // Add them all up and then return the estimate plus 33% for error margin 111 // and just general volatility of usage pattern. 112 estimatedCost := afterSiafundFeesEstimate.Add(txnFees) 113 estimatedCost = estimatedCost.Add(estimatedCost.Div64(3)) 114 115 // Check for a sane minimum. The contractor should not be forming contracts 116 // with less than 'fileContractMinimumFunding / (num contracts)' of the 117 // value of the allowance. 118 minimum := allowance.Funds.MulFloat(fileContractMinimumFunding).Div64(allowance.Hosts) 119 if estimatedCost.Cmp(minimum) < 0 { 120 estimatedCost = minimum 121 } 122 return estimatedCost, nil 123 } 124 125 // managedInterruptContractMaintenance will issue an interrupt signal to any 126 // running maintenance, stopping that maintenance. If there are multiple threads 127 // running maintenance, they will all be stopped. 128 func (c *Contractor) managedInterruptContractMaintenance() { 129 // Spin up a thread to grab the maintenance lock. Signal that the lock was 130 // acquired after the lock is acquired. 131 gotLock := make(chan struct{}) 132 go func() { 133 c.maintenanceLock.Lock() 134 close(gotLock) 135 c.maintenanceLock.Unlock() 136 }() 137 138 // There may be multiple threads contending for the maintenance lock. Issue 139 // interrupts repeatedly until we get a signal that the maintenance lock has 140 // been acquired. 141 for { 142 select { 143 case <-gotLock: 144 return 145 case c.interruptMaintenance <- struct{}{}: 146 } 147 } 148 } 149 150 // managedMarkContractsUtility checks every active contract in the contractor and 151 // figures out whether the contract is useful for uploading, and whether the 152 // contract should be renewed. 153 func (c *Contractor) managedMarkContractsUtility() error { 154 // Pull a new set of hosts from the hostdb that could be used as a new set 155 // to match the allowance. The lowest scoring host of these new hosts will 156 // be used as a baseline for determining whether our existing contracts are 157 // worthwhile. 158 c.mu.RLock() 159 hostCount := int(c.allowance.Hosts) 160 c.mu.RUnlock() 161 hosts, err := c.hdb.RandomHosts(hostCount+randomHostsBufferForScore, nil) 162 if err != nil { 163 return err 164 } 165 166 // Find the minimum score that a host is allowed to have to be considered 167 // good for upload. 168 var minScore types.Currency 169 if len(hosts) > 0 { 170 lowestScore := c.hdb.ScoreBreakdown(hosts[0]).Score 171 for i := 1; i < len(hosts); i++ { 172 score := c.hdb.ScoreBreakdown(hosts[i]).Score 173 if score.Cmp(lowestScore) < 0 { 174 lowestScore = score 175 } 176 } 177 // Set the minimum acceptable score to a factor of the lowest score. 178 minScore = lowestScore.Div(scoreLeeway) 179 } 180 181 // Update utility fields for each contract. 182 for _, contract := range c.staticContracts.ViewAll() { 183 utility := func() (u modules.ContractUtility) { 184 // Start the contract in good standing if the utility wasn't 185 // locked. 186 if !u.Locked { 187 u.GoodForUpload = true 188 u.GoodForRenew = true 189 } 190 191 host, exists := c.hdb.Host(contract.HostPublicKey) 192 // Contract has no utility if the host is not in the database. 193 if !exists { 194 u.GoodForUpload = false 195 u.GoodForRenew = false 196 return 197 } 198 // Contract has no utility if the score is poor. 199 if !minScore.IsZero() && c.hdb.ScoreBreakdown(host).Score.Cmp(minScore) < 0 { 200 u.GoodForUpload = false 201 u.GoodForRenew = false 202 return 203 } 204 // Contract has no utility if the host is offline. 205 if isOffline(host) { 206 u.GoodForUpload = false 207 u.GoodForRenew = false 208 return 209 } 210 // Contract should not be used for uploading if the time has come to 211 // renew the contract. 212 c.mu.RLock() 213 blockHeight := c.blockHeight 214 renewWindow := c.allowance.RenewWindow 215 c.mu.RUnlock() 216 if blockHeight+renewWindow >= contract.EndHeight { 217 u.GoodForUpload = false 218 return 219 } 220 return 221 }() 222 223 // Apply changes. 224 err := c.managedUpdateContractUtility(contract.ID, utility) 225 if err != nil { 226 return err 227 } 228 } 229 return nil 230 } 231 232 // managedNewContract negotiates an initial file contract with the specified 233 // host, saves it, and returns it. 234 func (c *Contractor) managedNewContract(host modules.HostDBEntry, contractFunding types.Currency, endHeight types.BlockHeight) (modules.RenterContract, error) { 235 // reject hosts that are too expensive 236 if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 237 return modules.RenterContract{}, errTooExpensive 238 } 239 // cap host.MaxCollateral 240 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 241 host.MaxCollateral = maxCollateral 242 } 243 244 // get an address to use for negotiation 245 uc, err := c.wallet.NextAddress() 246 if err != nil { 247 return modules.RenterContract{}, err 248 } 249 250 // create contract params 251 c.mu.RLock() 252 params := proto.ContractParams{ 253 Host: host, 254 Funding: contractFunding, 255 StartHeight: c.blockHeight, 256 EndHeight: endHeight, 257 RefundAddress: uc.UnlockHash(), 258 } 259 c.mu.RUnlock() 260 261 // create transaction builder 262 txnBuilder, err := c.wallet.StartTransaction() 263 if err != nil { 264 return modules.RenterContract{}, err 265 } 266 267 contract, err := c.staticContracts.FormContract(params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan()) 268 if err != nil { 269 txnBuilder.Drop() 270 return modules.RenterContract{}, err 271 } 272 273 // Add a mapping from the contract's id to the public key of the host. 274 c.mu.Lock() 275 c.contractIDToPubKey[contract.ID] = contract.HostPublicKey 276 _, exists := c.pubKeysToContractID[string(contract.HostPublicKey.Key)] 277 if exists { 278 c.mu.Unlock() 279 txnBuilder.Drop() 280 return modules.RenterContract{}, fmt.Errorf("We already have a contract with host %v", contract.HostPublicKey) 281 } 282 c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID 283 c.mu.Unlock() 284 285 contractValue := contract.RenterFunds 286 c.log.Printf("Formed contract %v with %v for %v", contract.ID, host.NetAddress, contractValue.HumanString()) 287 return contract, nil 288 } 289 290 // managedPrunePubkeyMap will delete any pubkeys in the pubKeysToContractID map 291 // that no longer map to an active contract. 292 func (c *Contractor) managedPrunePubkeyMap() { 293 allContracts := c.staticContracts.ViewAll() 294 pks := make(map[string]struct{}) 295 for _, c := range allContracts { 296 pks[string(c.HostPublicKey.Key)] = struct{}{} 297 } 298 c.mu.Lock() 299 for pk := range c.pubKeysToContractID { 300 if _, exists := pks[pk]; !exists { 301 delete(c.pubKeysToContractID, pk) 302 } 303 } 304 c.mu.Unlock() 305 } 306 307 // managedRenew negotiates a new contract for data already stored with a host. 308 // It returns the new contract. This is a blocking call that performs network 309 // I/O. 310 func (c *Contractor) managedRenew(sc *proto.SafeContract, contractFunding types.Currency, newEndHeight types.BlockHeight) (modules.RenterContract, error) { 311 // For convenience 312 contract := sc.Metadata() 313 // Sanity check - should not be renewing a bad contract. 314 utility, ok := c.managedContractUtility(contract.ID) 315 if !ok || !utility.GoodForRenew { 316 c.log.Critical(fmt.Sprintf("Renewing a contract that has been marked as !GoodForRenew %v/%v", 317 ok, utility.GoodForRenew)) 318 } 319 320 // Fetch the host associated with this contract. 321 host, ok := c.hdb.Host(contract.HostPublicKey) 322 if !ok { 323 return modules.RenterContract{}, errors.New("no record of that host") 324 } else if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 325 return modules.RenterContract{}, errTooExpensive 326 } 327 // cap host.MaxCollateral 328 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 329 host.MaxCollateral = maxCollateral 330 } 331 332 // get an address to use for negotiation 333 uc, err := c.wallet.NextAddress() 334 if err != nil { 335 return modules.RenterContract{}, err 336 } 337 338 // create contract params 339 c.mu.RLock() 340 params := proto.ContractParams{ 341 Host: host, 342 Funding: contractFunding, 343 StartHeight: c.blockHeight, 344 EndHeight: newEndHeight, 345 RefundAddress: uc.UnlockHash(), 346 } 347 c.mu.RUnlock() 348 349 // execute negotiation protocol 350 txnBuilder, err := c.wallet.StartTransaction() 351 if err != nil { 352 return modules.RenterContract{}, err 353 } 354 newContract, err := c.staticContracts.Renew(sc, params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan()) 355 if err != nil { 356 txnBuilder.Drop() // return unused outputs to wallet 357 return modules.RenterContract{}, err 358 } 359 360 // Add a mapping from the contract's id to the public key of the host. This 361 // will destroy the previous mapping from pubKey to contract id but other 362 // modules are only interested in the most recent contract anyway. 363 c.mu.Lock() 364 c.contractIDToPubKey[newContract.ID] = newContract.HostPublicKey 365 c.pubKeysToContractID[string(newContract.HostPublicKey.Key)] = newContract.ID 366 c.mu.Unlock() 367 368 return newContract, nil 369 } 370 371 // managedRenewContract will use the renew instructions to renew a contract, 372 // returning the amount of money that was put into the contract for renewal. 373 func (c *Contractor) managedRenewContract(renewInstructions fileContractRenewal, currentPeriod types.BlockHeight, allowance modules.Allowance, blockHeight types.BlockHeight) (fundsSpent types.Currency, err error) { 374 // Pull the variables out of the renewal. 375 id := renewInstructions.id 376 amount := renewInstructions.amount 377 378 // Mark the contract as being renewed, and defer logic to unmark it 379 // once renewing is complete. 380 c.mu.Lock() 381 c.renewing[id] = true 382 c.mu.Unlock() 383 defer func() { 384 c.mu.Lock() 385 delete(c.renewing, id) 386 c.mu.Unlock() 387 }() 388 389 // Wait for any active editors and downloaders to finish for this 390 // contract, and then grab the latest revision. 391 c.mu.RLock() 392 e, eok := c.editors[id] 393 d, dok := c.downloaders[id] 394 c.mu.RUnlock() 395 if eok { 396 e.invalidate() 397 } 398 if dok { 399 d.invalidate() 400 } 401 402 // Fetch the contract that we are renewing. 403 oldContract, exists := c.staticContracts.Acquire(id) 404 if !exists { 405 return types.ZeroCurrency, errors.New("contract no longer exists") 406 } 407 // Return the contract if it's not useful for renewing. 408 oldUtility, ok := c.managedContractUtility(id) 409 if !ok || !oldUtility.GoodForRenew { 410 c.log.Printf("Contract %v slated for renew is marked not good for renew %v/%v", 411 id, ok, oldUtility.GoodForRenew) 412 c.staticContracts.Return(oldContract) 413 return types.ZeroCurrency, errors.New("contract is marked not good for renew") 414 } 415 416 // Calculate endHeight for renewed contracts 417 endHeight := currentPeriod + allowance.Period 418 419 // Perform the actual renew. If the renew fails, return the 420 // contract. If the renew fails we check how often it has failed 421 // before. Once it has failed for a certain number of blocks in a 422 // row and reached its second half of the renew window, we give up 423 // on renewing it and set goodForRenew to false. 424 newContract, errRenew := c.managedRenew(oldContract, amount, endHeight) 425 if errRenew != nil { 426 // Increment the number of failed renews for the contract if it 427 // was the host's fault. 428 if modules.IsHostsFault(errRenew) { 429 c.mu.Lock() 430 c.numFailedRenews[oldContract.Metadata().ID]++ 431 c.mu.Unlock() 432 } 433 434 // Check if contract has to be replaced. 435 md := oldContract.Metadata() 436 c.mu.RLock() 437 numRenews, failedBefore := c.numFailedRenews[md.ID] 438 c.mu.RUnlock() 439 secondHalfOfWindow := blockHeight+allowance.RenewWindow/2 >= md.EndHeight 440 replace := numRenews >= consecutiveRenewalsBeforeReplacement 441 if failedBefore && secondHalfOfWindow && replace { 442 oldUtility.GoodForRenew = false 443 oldUtility.GoodForUpload = false 444 oldUtility.Locked = true 445 err := oldContract.UpdateUtility(oldUtility) 446 if err != nil { 447 c.log.Println("WARN: failed to mark contract as !goodForRenew:", err) 448 } 449 c.log.Printf("WARN: failed to renew %v, marked as bad: %v\n", 450 oldContract.Metadata().HostPublicKey, errRenew) 451 c.staticContracts.Return(oldContract) 452 return types.ZeroCurrency, errors.AddContext(errRenew, "contract marked as bad for too many consecutive failed renew attempts") 453 } 454 455 // Seems like it doesn't have to be replaced yet. Log the 456 // failure and number of renews that have failed so far. 457 c.log.Printf("WARN: failed to renew contract %v [%v]: %v\n", 458 oldContract.Metadata().HostPublicKey, numRenews, errRenew) 459 c.staticContracts.Return(oldContract) 460 return types.ZeroCurrency, errors.AddContext(errRenew, "contract renewal with host was unsuccessful") 461 } 462 c.log.Printf("Renewed contract %v\n", id) 463 464 // Update the utility values for the new contract, and for the old 465 // contract. 466 newUtility := modules.ContractUtility{ 467 GoodForUpload: true, 468 GoodForRenew: true, 469 } 470 if err := c.managedUpdateContractUtility(newContract.ID, newUtility); err != nil { 471 c.log.Println("Failed to update the contract utilities", err) 472 return amount, nil // Error is not returned because the renew succeeded. 473 } 474 oldUtility.GoodForRenew = false 475 oldUtility.GoodForUpload = false 476 if err := oldContract.UpdateUtility(oldUtility); err != nil { 477 c.log.Println("Failed to update the contract utilities", err) 478 return amount, nil // Error is not returned because the renew succeeded. 479 } 480 481 // Lock the contractor as we update it to use the new contract 482 // instead of the old contract. 483 c.mu.Lock() 484 defer c.mu.Unlock() 485 // Delete the old contract. 486 c.staticContracts.Delete(oldContract) 487 // Store the contract in the record of historic contracts. 488 c.oldContracts[id] = oldContract.Metadata() 489 // Save the contractor. 490 err = c.saveSync() 491 if err != nil { 492 c.log.Println("Failed to save the contractor after creating a new contract.") 493 } 494 return amount, nil 495 } 496 497 // threadedContractMaintenance checks the set of contracts that the contractor 498 // has against the allownace, renewing any contracts that need to be renewed, 499 // dropping contracts which are no longer worthwhile, and adding contracts if 500 // there are not enough. 501 // 502 // Between each network call, the thread checks whether a maintenance interrupt 503 // signal is being sent. If so, maintenance returns, yielding to whatever thread 504 // issued the interrupt. 505 func (c *Contractor) threadedContractMaintenance() { 506 // Threading protection. 507 err := c.tg.Add() 508 if err != nil { 509 return 510 } 511 defer c.tg.Done() 512 513 // Archive contracts that need to be archived before doing additional 514 // maintenance, and then prune the pubkey map. 515 c.managedArchiveContracts() 516 c.managedPrunePubkeyMap() 517 518 // Nothing to do if there are no hosts. 519 c.mu.RLock() 520 wantedHosts := c.allowance.Hosts 521 c.mu.RUnlock() 522 if wantedHosts <= 0 { 523 return 524 } 525 526 // Only one instance of this thread should be running at a time. Under 527 // normal conditions, fine to return early if another thread is already 528 // doing maintenance. The next block will trigger another round. Under 529 // testing, control is insufficient if the maintenance loop isn't guaranteed 530 // to run. 531 if build.Release == "testing" { 532 c.maintenanceLock.Lock() 533 } else if !c.maintenanceLock.TryLock() { 534 return 535 } 536 defer c.maintenanceLock.Unlock() 537 538 // Update the utility fields for this contract based on the most recent 539 // hostdb. 540 if err := c.managedMarkContractsUtility(); err != nil { 541 c.log.Println("WARNING: wasn't able to mark contracts", err) 542 return 543 } 544 545 // Create the renewSet and refreshSet. Each is a list of contracts that need 546 // to be renewed, paired with the amount of money to use in each renewal. 547 // 548 // The renewSet is specifically contracts which are being renewed because 549 // they are about to expire. And the refreshSet is contracts that are being 550 // renewed because they are out of money. 551 // 552 // The contractor will prioritize contracts in the renewSet over contracts 553 // in the refreshSet. If the wallet does not have enough money, or if the 554 // allowance does not have enough money, the contractor will prefer to save 555 // data in the long term rather than renew a contract. 556 var renewSet []fileContractRenewal 557 var refreshSet []fileContractRenewal 558 559 // The rest of this function needs to know a few of the stateful variables 560 // from the contractor, build those up under a lock so that the rest of the 561 // function can execute without lock contention. 562 c.mu.RLock() 563 currentPeriod := c.currentPeriod 564 allowance := c.allowance 565 blockHeight := c.blockHeight 566 c.mu.RUnlock() 567 endHeight := currentPeriod + allowance.Period 568 569 // Iterate through the contracts again, figuring out which contracts to 570 // renew and how much extra funds to renew them with. 571 for _, contract := range c.staticContracts.ViewAll() { 572 // Skip any contracts which do not exist or are otherwise unworthy for 573 // renewal. 574 utility, ok := c.managedContractUtility(contract.ID) 575 if !ok || !utility.GoodForRenew { 576 continue 577 } 578 579 // If the contract needs to be renewed because it is about to expire, 580 // calculate a spending for the contract that is proportional to how 581 // much money was spend on the contract throughout this billing cycle 582 // (which is now ending). 583 if blockHeight+allowance.RenewWindow >= contract.EndHeight { 584 renewAmount, err := c.managedEstimateRenewFundingRequirements(contract, blockHeight, allowance) 585 if err != nil { 586 continue 587 } 588 renewSet = append(renewSet, fileContractRenewal{ 589 id: contract.ID, 590 amount: renewAmount, 591 }) 592 continue 593 } 594 595 // Check if the contract is empty. We define a contract as being empty 596 // if less than 'minContractFundRenewalThreshold' funds are remaining 597 // (3% at time of writing), or if there is less than 3 sectors worth of 598 // storage+upload+download remaining. 599 host, _ := c.hdb.Host(contract.HostPublicKey) 600 blockBytes := types.NewCurrency64(modules.SectorSize * uint64(allowance.Period)) 601 sectorStoragePrice := host.StoragePrice.Mul(blockBytes) 602 sectorUploadBandwidthPrice := host.UploadBandwidthPrice.Mul64(modules.SectorSize) 603 sectorDownloadBandwidthPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize) 604 sectorBandwidthPrice := sectorUploadBandwidthPrice.Add(sectorDownloadBandwidthPrice) 605 sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice) 606 percentRemaining, _ := big.NewRat(0, 1).SetFrac(contract.RenterFunds.Big(), contract.TotalCost.Big()).Float64() 607 if contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0 || percentRemaining < minContractFundRenewalThreshold { 608 // Renew the contract with double the amount of funds that the 609 // contract had previously. The reason that we double the funding 610 // instead of doing anything more clever is that we don't know what 611 // the usage pattern has been. The spending could have all occured 612 // in one burst recently, and the user might need a contract that 613 // has substantially more money in it. 614 // 615 // We double so that heavily used contracts can grow in funding 616 // quickly without consuming too many transaction fees, however this 617 // does mean that a larger percentage of funds get locked away from 618 // the user in the event that the user stops uploading immediately 619 // after the renew. 620 refreshSet = append(refreshSet, fileContractRenewal{ 621 id: contract.ID, 622 amount: contract.TotalCost.Mul64(2), 623 }) 624 } 625 } 626 if len(renewSet) != 0 { 627 c.log.Printf("renewing %v contracts", len(renewSet)) 628 } 629 630 // Remove contracts that are not scheduled for renew from the 631 // firstFailedRenew map. We do this by making a new map entirely and copying 632 // over all the elements that still matter. 633 c.mu.Lock() 634 newFirstFailedRenew := make(map[types.FileContractID]types.BlockHeight) 635 for _, r := range renewSet { 636 if _, exists := c.numFailedRenews[r.id]; exists { 637 newFirstFailedRenew[r.id] = c.numFailedRenews[r.id] 638 } 639 } 640 c.numFailedRenews = newFirstFailedRenew 641 c.mu.Unlock() 642 643 // Depend on the PeriodSpending function to get a breakdown of spending in 644 // the contractor. Then use that to determine how many funds remain 645 // available in the allowance for renewals. 646 spending := c.PeriodSpending() 647 var fundsRemaining types.Currency 648 // Check for an underflow. This can happen if the user reduced their 649 // allowance at some point to less than what we've already spent. 650 if spending.TotalAllocated.Cmp(allowance.Funds) < 0 { 651 fundsRemaining = allowance.Funds.Sub(spending.TotalAllocated) 652 } 653 654 // Go through the contracts we've assembled for renewal. Any contracts that 655 // need to be renewed because they are expiring (renewSet) get priority over 656 // contracts that need to be renewed because they have exhausted their funds 657 // (refreshSet). If there is not enough money available, the more expensive 658 // contracts will be skipped. 659 // 660 // TODO: We need some sort of global warning system so that we can alert the 661 // user to the fact that they do not have enough money to keep their 662 // contracts going in the event that we run out of funds. 663 for _, renewal := range renewSet { 664 // Skip this renewal if we don't have enough funds remaining. 665 if renewal.amount.Cmp(fundsRemaining) > 0 { 666 continue 667 } 668 669 // Renew one contract. The error is ignored because the renew function 670 // already will have logged the error, and in the event of an error, 671 // 'fundsSpent' will return '0'. 672 fundsSpent, _ := c.managedRenewContract(renewal, currentPeriod, allowance, blockHeight) 673 fundsRemaining = fundsRemaining.Sub(fundsSpent) 674 675 // Return here if an interrupt or kill signal has been sent. 676 select { 677 case <-c.tg.StopChan(): 678 return 679 case <-c.interruptMaintenance: 680 return 681 default: 682 } 683 } 684 for _, renewal := range refreshSet { 685 // Skip this renewal if we don't have enough funds remaining. 686 if renewal.amount.Cmp(fundsRemaining) > 0 { 687 continue 688 } 689 690 // Renew one contract. The error is ignored because the renew function 691 // already will have logged the error, and in the event of an error, 692 // 'fundsSpent' will return '0'. 693 fundsSpent, _ := c.managedRenewContract(renewal, currentPeriod, allowance, blockHeight) 694 fundsRemaining = fundsRemaining.Sub(fundsSpent) 695 696 // Return here if an interrupt or kill signal has been sent. 697 select { 698 case <-c.tg.StopChan(): 699 return 700 case <-c.interruptMaintenance: 701 return 702 default: 703 } 704 } 705 706 // Count the number of contracts which are good for uploading, and then make 707 // more as needed to fill the gap. 708 uploadContracts := 0 709 for _, id := range c.staticContracts.IDs() { 710 if cu, ok := c.managedContractUtility(id); ok && cu.GoodForUpload { 711 uploadContracts++ 712 } 713 } 714 c.mu.RLock() 715 neededContracts := int(c.allowance.Hosts) - uploadContracts 716 c.mu.RUnlock() 717 if neededContracts <= 0 { 718 return 719 } 720 721 // Assemble an exclusion list that includes all of the hosts that we already 722 // have contracts with, then select a new batch of hosts to attempt contract 723 // formation with. 724 c.mu.RLock() 725 var exclude []types.SiaPublicKey 726 for _, contract := range c.staticContracts.ViewAll() { 727 exclude = append(exclude, contract.HostPublicKey) 728 } 729 initialContractFunds := c.allowance.Funds.Div64(c.allowance.Hosts).Div64(3) 730 c.mu.RUnlock() 731 hosts, err := c.hdb.RandomHosts(neededContracts*2+randomHostsBufferForScore, exclude) 732 if err != nil { 733 c.log.Println("WARN: not forming new contracts:", err) 734 return 735 } 736 737 // Form contracts with the hosts one at a time, until we have enough 738 // contracts. 739 for _, host := range hosts { 740 // Determine if we have enough money to form a new contract. 741 if fundsRemaining.Cmp(initialContractFunds) < 0 { 742 c.log.Println("WARN: need to form new contracts, but unable to because of a low allowance") 743 break 744 } 745 746 // Attempt forming a contract with this host. 747 newContract, err := c.managedNewContract(host, initialContractFunds, endHeight) 748 if err != nil { 749 c.log.Printf("Attempted to form a contract with %v, but negotiation failed: %v\n", host.NetAddress, err) 750 continue 751 } 752 753 // Add this contract to the contractor and save. 754 err = c.managedUpdateContractUtility(newContract.ID, modules.ContractUtility{ 755 GoodForUpload: true, 756 GoodForRenew: true, 757 }) 758 if err != nil { 759 c.log.Println("Failed to update the contract utilities", err) 760 return 761 } 762 c.mu.Lock() 763 err = c.saveSync() 764 c.mu.Unlock() 765 if err != nil { 766 c.log.Println("Unable to save the contractor:", err) 767 } 768 769 // Quit the loop if we've replaced all needed contracts. 770 neededContracts-- 771 if neededContracts <= 0 { 772 break 773 } 774 775 // Soft sleep before making the next contract. 776 select { 777 case <-c.tg.StopChan(): 778 return 779 case <-c.interruptMaintenance: 780 return 781 default: 782 } 783 } 784 } 785 786 // managedUpdateContractUtility is a helper function that acquires a contract, updates 787 // its ContractUtility and returns the contract again. 788 func (c *Contractor) managedUpdateContractUtility(id types.FileContractID, utility modules.ContractUtility) error { 789 safeContract, ok := c.staticContracts.Acquire(id) 790 if !ok { 791 return errors.New("failed to acquire contract for update") 792 } 793 defer c.staticContracts.Return(safeContract) 794 return safeContract.UpdateUtility(utility) 795 }