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