github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/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 "math/big" 10 "time" 11 12 "github.com/Synthesix/Sia/build" 13 "github.com/Synthesix/Sia/modules" 14 "github.com/Synthesix/Sia/modules/renter/proto" 15 "github.com/Synthesix/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 // managedInterruptContractMaintenance will issue an interrupt signal to any 32 // running maintenance, stopping that maintenance. If there are multiple threads 33 // running maintenance, they will all be stopped. 34 func (c *Contractor) managedInterruptContractMaintenance() { 35 // Spin up a thread to grab the maintenance lock. Signal that the lock was 36 // acquired after the lock is acquired. 37 gotLock := make(chan struct{}) 38 go func() { 39 c.maintenanceLock.Lock() 40 close(gotLock) 41 c.maintenanceLock.Unlock() 42 }() 43 44 // There may be multiple threads contending for the maintenance lock. Issue 45 // interrupts repeatedly until we get a signal that the maintenance lock has 46 // been acquired. 47 for { 48 select { 49 case <-gotLock: 50 return 51 case c.interruptMaintenance <- struct{}{}: 52 } 53 } 54 } 55 56 // managedMarkContractsUtility checks every active contract in the contractor and 57 // figures out whether the contract is useful for uploading, and whehter the 58 // contract should be renewed. 59 func (c *Contractor) managedMarkContractsUtility() { 60 // Pull a new set of hosts from the hostdb that could be used as a new set 61 // to match the allowance. The lowest scoring host of these new hosts will 62 // be used as a baseline for determining whether our existing contracts are 63 // worthwhile. 64 c.mu.RLock() 65 hostCount := int(c.allowance.Hosts) 66 c.mu.RUnlock() 67 hosts := c.hdb.RandomHosts(hostCount+minScoreHostBuffer, nil) 68 69 // Find the minimum score that a host is allowed to have to be considered 70 // good for upload. 71 var minScore types.Currency 72 if len(hosts) > 0 { 73 lowestScore := c.hdb.ScoreBreakdown(hosts[0]).Score 74 for i := 1; i < len(hosts); i++ { 75 score := c.hdb.ScoreBreakdown(hosts[i]).Score 76 if score.Cmp(lowestScore) < 0 { 77 lowestScore = score 78 } 79 } 80 // Set the minimum acceptable score to a factor of the lowest score. 81 minScore = lowestScore.Div(scoreLeeway) 82 } 83 84 // Update utility fields for each contract. 85 for _, contract := range c.contracts.ViewAll() { 86 utility := func() (u modules.ContractUtility) { 87 // Start the contract in good standing. 88 u.GoodForUpload = true 89 u.GoodForRenew = true 90 91 host, exists := c.hdb.Host(contract.HostPublicKey) 92 // Contract has no utility if the host is not in the database. 93 if !exists { 94 u.GoodForUpload = false 95 u.GoodForRenew = false 96 return 97 } 98 // Contract has no utility if the score is poor. 99 if !minScore.IsZero() && c.hdb.ScoreBreakdown(host).Score.Cmp(minScore) < 0 { 100 u.GoodForUpload = false 101 u.GoodForRenew = false 102 return 103 } 104 // Contract has no utility if the host is offline. 105 if isOffline(host) { 106 u.GoodForUpload = false 107 u.GoodForRenew = false 108 return 109 } 110 // Contract has no utility if renew has already completed. (grab some 111 // extra values while we have the mutex) 112 c.mu.RLock() 113 blockHeight := c.blockHeight 114 renewWindow := c.allowance.RenewWindow 115 _, renewedPreviously := c.renewedIDs[contract.ID] 116 c.mu.RUnlock() 117 if renewedPreviously { 118 u.GoodForUpload = false 119 u.GoodForRenew = false 120 return 121 } 122 123 // Contract should not be used for uploading if the time has come to 124 // renew the contract. 125 if blockHeight+renewWindow >= contract.EndHeight { 126 u.GoodForUpload = false 127 return 128 } 129 return 130 }() 131 132 // Apply changes. 133 c.mu.Lock() 134 c.contractUtilities[contract.ID] = utility 135 c.mu.Unlock() 136 } 137 } 138 139 // managedNewContract negotiates an initial file contract with the specified 140 // host, saves it, and returns it. 141 func (c *Contractor) managedNewContract(host modules.HostDBEntry, contractFunding types.Currency, endHeight types.BlockHeight) (modules.RenterContract, error) { 142 // reject hosts that are too expensive 143 if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 144 return modules.RenterContract{}, errTooExpensive 145 } 146 // cap host.MaxCollateral 147 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 148 host.MaxCollateral = maxCollateral 149 } 150 151 // get an address to use for negotiation 152 uc, err := c.wallet.NextAddress() 153 if err != nil { 154 return modules.RenterContract{}, err 155 } 156 157 // create contract params 158 c.mu.RLock() 159 params := proto.ContractParams{ 160 Host: host, 161 Funding: contractFunding, 162 StartHeight: c.blockHeight, 163 EndHeight: endHeight, 164 RefundAddress: uc.UnlockHash(), 165 } 166 c.mu.RUnlock() 167 168 // create transaction builder 169 txnBuilder := c.wallet.StartTransaction() 170 171 contract, err := c.contracts.FormContract(params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan()) 172 if err != nil { 173 txnBuilder.Drop() 174 return modules.RenterContract{}, err 175 } 176 177 contractValue := contract.RenterFunds 178 c.log.Printf("Formed contract with %v for %v", host.NetAddress, contractValue.HumanString()) 179 return contract, nil 180 } 181 182 // managedRenew negotiates a new contract for data already stored with a host. 183 // It returns the new contract. This is a blocking call that performs network 184 // I/O. 185 func (c *Contractor) managedRenew(sc *proto.SafeContract, contractFunding types.Currency, newEndHeight types.BlockHeight) (modules.RenterContract, error) { 186 // For convenience 187 contract := sc.Metadata() 188 // Sanity check - should not be renewing a bad contract. 189 c.mu.RLock() 190 utility := c.contractUtilities[contract.ID] 191 c.mu.RUnlock() 192 if !utility.GoodForRenew { 193 c.log.Critical("Renewing a contract that has been marked as !GoodForRenew") 194 } 195 196 // Fetch the host associated with this contract. 197 host, ok := c.hdb.Host(contract.HostPublicKey) 198 if !ok { 199 return modules.RenterContract{}, errors.New("no record of that host") 200 } else if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 201 return modules.RenterContract{}, errTooExpensive 202 } 203 // cap host.MaxCollateral 204 if host.MaxCollateral.Cmp(maxCollateral) > 0 { 205 host.MaxCollateral = maxCollateral 206 } 207 208 // get an address to use for negotiation 209 uc, err := c.wallet.NextAddress() 210 if err != nil { 211 return modules.RenterContract{}, err 212 } 213 214 // create contract params 215 c.mu.RLock() 216 params := proto.ContractParams{ 217 Host: host, 218 Funding: contractFunding, 219 StartHeight: c.blockHeight, 220 EndHeight: newEndHeight, 221 RefundAddress: uc.UnlockHash(), 222 } 223 c.mu.RUnlock() 224 225 // execute negotiation protocol 226 txnBuilder := c.wallet.StartTransaction() 227 newContract, err := c.contracts.Renew(sc, params, txnBuilder, c.tpool, c.hdb, c.tg.StopChan()) 228 if err != nil { 229 txnBuilder.Drop() // return unused outputs to wallet 230 return modules.RenterContract{}, err 231 } 232 233 return newContract, nil 234 } 235 236 // threadedContractMaintenance checks the set of contracts that the contractor 237 // has against the allownace, renewing any contracts that need to be renewed, 238 // dropping contracts which are no longer worthwhile, and adding contracts if 239 // there are not enough. 240 // 241 // Between each network call, the thread checks whether a maintenance iterrupt 242 // signal is being sent. If so, maintannce returns, yielding to whatever thread 243 // issued the interrupt. 244 func (c *Contractor) threadedContractMaintenance() { 245 // Threading protection. 246 err := c.tg.Add() 247 if err != nil { 248 return 249 } 250 defer c.tg.Done() 251 // Nothing to do if there are no hosts. 252 c.mu.RLock() 253 wantedHosts := c.allowance.Hosts 254 c.mu.RUnlock() 255 if wantedHosts <= 0 { 256 return 257 } 258 // Only one instance of this thread should be running at a time. Under 259 // normal conditions, fine to return early if another thread is already 260 // doing maintenance. The next block will trigger another round. Under 261 // testing, control is insufficient if the maintenance loop isn't guaranteed 262 // to run. 263 if build.Release == "testing" { 264 c.maintenanceLock.Lock() 265 } else if !c.maintenanceLock.TryLock() { 266 return 267 } 268 defer c.maintenanceLock.Unlock() 269 270 // Update the utility fields for this contract based on the most recent 271 // hostdb. 272 c.managedMarkContractsUtility() 273 274 // Figure out which contracts need to be renewed, and while we have the 275 // lock, figure out the end height for the new contracts and also the amount 276 // to spend on each contract. 277 // 278 // refreshSet is used to mark contracts that need to be refreshed because 279 // they have run out of money. The refreshSet indicates how much currency 280 // was used previously in the contract line, and is used to figure out how 281 // much additional money to add in the refreshed contract. 282 // 283 // The actions inside this RLock are complex enough to merit wrapping them 284 // in a function where we can defer the unlock. 285 type renewal struct { 286 id types.FileContractID 287 amount types.Currency 288 } 289 var endHeight types.BlockHeight 290 var fundsAvailable types.Currency 291 var renewSet []renewal 292 refreshSet := make(map[types.FileContractID]struct{}) 293 func() { 294 c.mu.RLock() 295 defer c.mu.RUnlock() 296 297 // Grab the end height that should be used for the contracts. 298 endHeight = c.currentPeriod + c.allowance.Period 299 300 // Determine how many funds have been used already in this billing 301 // cycle, and how many funds are remaining. We have to calculate these 302 // numbers separately to avoid underflow, and then re-join them later to 303 // get the full picture for how many funds are available. 304 var fundsUsed types.Currency 305 for _, contract := range c.contracts.ViewAll() { 306 // Calculate the cost of the contract line. 307 contractLineCost := contract.TotalCost 308 // TODO: add previous contracts here 309 310 // Check if the contract is expiring. The funds in the contract are 311 // handled differently based on this information. 312 if c.blockHeight+c.allowance.RenewWindow >= contract.EndHeight { 313 // The contract is expiring. Some of the funds are locked down 314 // to renew the contract, and then the remaining funds can be 315 // allocated to 'availableFunds'. 316 fundsUsed = fundsUsed.Add(contractLineCost).Sub(contract.RenterFunds) 317 fundsAvailable = fundsAvailable.Add(contract.RenterFunds) 318 } else { 319 // The contract is not expiring. None of the funds in the 320 // contract are available to renew or form contracts. 321 fundsUsed = fundsUsed.Add(contractLineCost) 322 } 323 } 324 325 // Add any unspent funds from the allowance to the available funds. If 326 // the allowance has been decreased, it's possible that we actually need 327 // to reduce the number of funds available to compensate. 328 if fundsAvailable.Add(c.allowance.Funds).Cmp(fundsUsed) > 0 { 329 fundsAvailable = fundsAvailable.Add(c.allowance.Funds).Sub(fundsUsed) 330 } else { 331 // Figure out how much we need to remove from fundsAvailable to 332 // clear the allowance. 333 overspend := fundsUsed.Sub(c.allowance.Funds).Sub(fundsAvailable) 334 if fundsAvailable.Cmp(overspend) > 0 { 335 // We still have some funds available. 336 fundsAvailable = fundsAvailable.Sub(overspend) 337 } else { 338 // The overspend exceeds the available funds, set available 339 // funds to zero. 340 fundsAvailable = types.ZeroCurrency 341 } 342 } 343 344 // Iterate through the contracts again, figuring out which contracts to 345 // renew and how much extra funds to renew them with. 346 for _, contract := range c.contracts.ViewAll() { 347 if !c.contractUtilities[contract.ID].GoodForRenew { 348 continue 349 } 350 if c.blockHeight+c.allowance.RenewWindow >= contract.EndHeight { 351 // This contract needs to be renewed because it is going to 352 // expire soon. First step is to calculate how much money should 353 // be used in the renewal, based on how much of the contract 354 // funds (including previous contracts this billing cycle due to 355 // financial resets) were spent throughout this billing cycle. 356 // 357 // The amount we care about is the total amount that was spent 358 // on uploading, downloading, and storage throughout the billing 359 // cycle. This is calculated by starting with the total cost and 360 // subtracting out all of the fees, and then all of the unused 361 // money that was allocated (the RenterFunds). 362 renewAmount := contract.TotalCost.Sub(contract.ContractFee).Sub(contract.TxnFee).Sub(contract.SiafundFee).Sub(contract.RenterFunds) 363 // TODO: add previous contracts here 364 365 // Get an estimate for how much the fees will cost. 366 // 367 // TODO: Look up this host in the hostdb to figure out what the 368 // actual fees will be. 369 estimatedFees := contract.ContractFee.Add(contract.TxnFee).Add(contract.SiafundFee) 370 renewAmount = renewAmount.Add(estimatedFees) 371 372 // Determine if there is enough funds available to suppliement 373 // with a 33% bonus, and if there is, add a 33% bonus. 374 moneyBuffer := renewAmount.Div64(3) 375 if moneyBuffer.Cmp(fundsAvailable) < 0 { 376 renewAmount = renewAmount.Add(moneyBuffer) 377 fundsAvailable = fundsAvailable.Sub(moneyBuffer) 378 } else { 379 c.log.Println("WARN: performing a limited renew due to low allowance") 380 } 381 382 // The contract needs to be renewed because it is going to 383 // expire soon, and we need to refresh the time. 384 renewSet = append(renewSet, renewal{ 385 id: contract.ID, 386 amount: renewAmount, 387 }) 388 } else { 389 // Check if the contract has exhausted its funding and requires 390 // premature renewal. 391 c.mu.RUnlock() 392 host, _ := c.hdb.Host(contract.HostPublicKey) 393 c.mu.RLock() 394 395 // Skip this host if its prices are too high. 396 // managedMarkContractsUtility should make this redundant, but 397 // this is here for extra safety. 398 if host.StoragePrice.Cmp(maxStoragePrice) > 0 || host.UploadBandwidthPrice.Cmp(maxUploadPrice) > 0 { 399 continue 400 } 401 402 blockBytes := types.NewCurrency64(modules.SectorSize * uint64(contract.EndHeight-c.blockHeight)) 403 sectorStoragePrice := host.StoragePrice.Mul(blockBytes) 404 sectorBandwidthPrice := host.UploadBandwidthPrice.Mul64(modules.SectorSize) 405 sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice) 406 percentRemaining, _ := big.NewRat(0, 1).SetFrac(contract.RenterFunds.Big(), contract.TotalCost.Big()).Float64() 407 if contract.RenterFunds.Cmp(sectorPrice.Mul64(3)) < 0 || percentRemaining < minContractFundRenewalThreshold { 408 // This contract does need to be refreshed. Make sure there 409 // are enough funds available to perform the refresh, and 410 // then execute. 411 refreshAmount := contract.TotalCost.Mul64(2) 412 if refreshAmount.Cmp(fundsAvailable) < 0 { 413 refreshSet[contract.ID] = struct{}{} 414 renewSet = append(renewSet, renewal{ 415 id: contract.ID, 416 amount: refreshAmount, 417 }) 418 } else { 419 c.log.Println("WARN: cannot refresh empty contract due to low allowance.") 420 } 421 } 422 } 423 } 424 }() 425 if len(renewSet) != 0 { 426 c.log.Printf("renewing %v contracts", len(renewSet)) 427 } 428 429 // Loop through the contracts and renew them one-by-one. 430 for _, renewal := range renewSet { 431 // Pull the variables out of the renewal. 432 id := renewal.id 433 amount := renewal.amount 434 435 // Renew one contract. 436 func() { 437 // Mark the contract as being renewed, and defer logic to unmark it 438 // once renewing is complete. 439 c.mu.Lock() 440 c.renewing[id] = true 441 c.mu.Unlock() 442 defer func() { 443 c.mu.Lock() 444 delete(c.renewing, id) 445 c.mu.Unlock() 446 }() 447 448 // Wait for any active editors and downloaders to finish for this 449 // contract, and then grab the latest revision. 450 c.mu.RLock() 451 e, eok := c.editors[id] 452 d, dok := c.downloaders[id] 453 c.mu.RUnlock() 454 if eok { 455 e.invalidate() 456 } 457 if dok { 458 d.invalidate() 459 } 460 461 // Fetch the contract that we are renewing. 462 oldContract, exists := c.contracts.Acquire(id) 463 if !exists { 464 return 465 } 466 // Return the contract if it's not useful for renewing. 467 c.mu.RLock() 468 oldUtility := c.contractUtilities[id] 469 c.mu.RUnlock() 470 if !oldUtility.GoodForRenew { 471 c.log.Printf("Contract %v slated for renew is marked not good for renew", id) 472 c.contracts.Return(oldContract) 473 return 474 } 475 // Perform the actual renew. If the renew fails, return the 476 // contract. 477 newContract, err := c.managedRenew(oldContract, amount, endHeight) 478 if err != nil { 479 c.log.Printf("WARN: failed to renew contract %v: %v\n", id, err) 480 c.contracts.Return(oldContract) 481 return 482 } 483 c.log.Printf("Renewed contract %v\n", id) 484 485 // Update the utility values for the new contract, and for the old 486 // contract. 487 c.mu.Lock() 488 newUtility := modules.ContractUtility{ 489 GoodForUpload: true, 490 GoodForRenew: true, 491 } 492 c.contractUtilities[newContract.ID] = newUtility 493 oldUtility.GoodForRenew = false 494 oldUtility.GoodForUpload = false 495 c.contractUtilities[id] = oldUtility 496 c.mu.Unlock() 497 // If the contract is a mid-cycle renew, add the contract line to 498 // the new contract. The contract line is not included/extended if 499 // we are just renewing because the contract is expiring. 500 if _, exists := refreshSet[id]; exists { 501 // TODO: update PreviousContracts 502 } 503 504 // Lock the contractor as we update it to use the new contract 505 // instead of the old contract. 506 c.mu.Lock() 507 defer c.mu.Unlock() 508 // Delete the old contract. 509 c.contracts.Delete(oldContract) 510 // Store the contract in the record of historic contracts. 511 c.oldContracts[id] = oldContract.Metadata() 512 // Add a mapping from the old contract to the new contract. 513 c.renewedIDs[id] = newContract.ID 514 // Save the contractor. 515 err = c.saveSync() 516 if err != nil { 517 c.log.Println("Failed to save the contractor after creating a new contract.") 518 } 519 }() 520 521 // Soft sleep for a minute to allow all of the transactions to propagate 522 // the network. 523 select { 524 case <-c.tg.StopChan(): 525 return 526 case <-c.interruptMaintenance: 527 return 528 case <-time.After(contractFormationInterval): 529 } 530 } 531 532 // Quit in the event of shutdown. 533 select { 534 case <-c.tg.StopChan(): 535 return 536 case <-c.interruptMaintenance: 537 return 538 default: 539 } 540 541 // Count the number of contracts which are good for uploading, and then make 542 // more as needed to fill the gap. 543 c.mu.RLock() 544 uploadContracts := 0 545 for _, id := range c.contracts.IDs() { 546 if c.contractUtilities[id].GoodForUpload { 547 uploadContracts++ 548 } 549 } 550 neededContracts := int(c.allowance.Hosts) - uploadContracts 551 c.mu.RUnlock() 552 if neededContracts <= 0 { 553 return 554 } 555 556 // Assemble an exclusion list that includes all of the hosts that we already 557 // have contracts with, then select a new batch of hosts to attempt contract 558 // formation with. 559 c.mu.RLock() 560 var exclude []types.SiaPublicKey 561 for _, contract := range c.contracts.ViewAll() { 562 exclude = append(exclude, contract.HostPublicKey) 563 } 564 initialContractFunds := c.allowance.Funds.Div64(c.allowance.Hosts).Div64(3) 565 c.mu.RUnlock() 566 hosts := c.hdb.RandomHosts(neededContracts*2+10, exclude) 567 568 // Form contracts with the hosts one at a time, until we have enough 569 // contracts. 570 for _, host := range hosts { 571 // Determine if we have enough money to form a new contract. 572 if fundsAvailable.Cmp(initialContractFunds) < 0 { 573 c.log.Println("WARN: need to form new contracts, but unable to because of a low allowance") 574 break 575 } 576 577 // Attempt forming a contract with this host. 578 newContract, err := c.managedNewContract(host, initialContractFunds, endHeight) 579 if err != nil { 580 c.log.Printf("Attempted to form a contract with %v, but negotiation failed: %v\n", host.NetAddress, err) 581 continue 582 } 583 584 // Add this contract to the contractor and save. 585 c.mu.Lock() 586 c.contractUtilities[newContract.ID] = modules.ContractUtility{ 587 GoodForUpload: true, 588 GoodForRenew: true, 589 } 590 err = c.saveSync() 591 c.mu.Unlock() 592 if err != nil { 593 c.log.Println("Unable to save the contractor:", err) 594 } 595 596 // Quit the loop if we've replaced all needed contracts. 597 neededContracts-- 598 if neededContracts <= 0 { 599 break 600 } 601 602 // Soft sleep before making the next contract. 603 select { 604 case <-c.tg.StopChan(): 605 return 606 case <-c.interruptMaintenance: 607 return 608 case <-time.After(contractFormationInterval): 609 } 610 } 611 }