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