gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/contractor.go (about) 1 package contractor 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net" 8 "os" 9 "path/filepath" 10 "strings" 11 "sync" 12 "sync/atomic" 13 14 "gitlab.com/NebulousLabs/errors" 15 "gitlab.com/NebulousLabs/ratelimit" 16 "gitlab.com/NebulousLabs/threadgroup" 17 18 "gitlab.com/SkynetLabs/skyd/build" 19 "gitlab.com/SkynetLabs/skyd/skymodules" 20 "gitlab.com/SkynetLabs/skyd/skymodules/renter/proto" 21 "go.sia.tech/siad/crypto" 22 "go.sia.tech/siad/modules" 23 "go.sia.tech/siad/persist" 24 siasync "go.sia.tech/siad/sync" 25 "go.sia.tech/siad/types" 26 ) 27 28 var ( 29 errNilCS = errors.New("cannot create contractor with nil consensus set") 30 errNilHDB = errors.New("cannot create contractor with nil HostDB") 31 errNilTpool = errors.New("cannot create contractor with nil transaction pool") 32 errNilWallet = errors.New("cannot create contractor with nil wallet") 33 34 errHostNotFound = errors.New("host not found") 35 errContractNotFound = errors.New("contract not found") 36 37 // COMPATv1.0.4-lts 38 // metricsContractID identifies a special contract that contains aggregate 39 // financial metrics from older contractors 40 metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'} 41 ) 42 43 // emptyWorkerPool is the workerpool that a contractor is initialized with. 44 type emptyWorkerPool struct{} 45 46 // Worker implements the WorkerPool interface. 47 func (emptyWorkerPool) Worker(_ types.SiaPublicKey) (skymodules.Worker, error) { 48 return nil, errors.New("empty worker pool") 49 } 50 51 // A Contractor negotiates, revises, renews, and provides access to file 52 // contracts. 53 type Contractor struct { 54 // dependencies 55 staticAlerter *modules.GenericAlerter 56 staticCS modules.ConsensusSet 57 staticDeps modules.Dependencies 58 staticHDB skymodules.HostDB 59 staticLog *persist.Logger 60 staticTG threadgroup.ThreadGroup 61 staticTPool modules.TransactionPool 62 staticWallet modules.Wallet 63 staticWorkerPool skymodules.WorkerPool 64 65 mu sync.RWMutex 66 persistDir string 67 68 // Only one thread should be performing contract maintenance at a time. 69 staticInterruptMaintenance chan struct{} 70 maintenanceLock siasync.TryMutex 71 72 // Only one thread should be scanning the blockchain for recoverable 73 // contracts at a time. 74 atomicScanInProgress uint32 75 atomicRecoveryScanHeight int64 76 77 allowance skymodules.Allowance 78 blockHeight types.BlockHeight 79 synced chan struct{} 80 currentPeriod types.BlockHeight 81 lastChange modules.ConsensusChangeID 82 83 // recentRecoveryChange is the first ConsensusChange that was missed while 84 // trying to find recoverable contracts. This is where we need to start 85 // rescanning the blockchain for recoverable contracts the next time the wallet 86 // is unlocked. 87 recentRecoveryChange modules.ConsensusChangeID 88 89 downloaders map[types.FileContractID]*hostDownloader 90 editors map[types.FileContractID]*hostEditor 91 sessions map[types.FileContractID]*hostSession 92 numFailedRenews map[types.FileContractID]types.BlockHeight 93 renewing map[types.FileContractID]bool // prevent revising during renewal 94 95 // pubKeysToContractID is a map of host pubkeys to the latest contract ID 96 // that is formed with the host. The contract also has to have an end height 97 // in the future 98 pubKeysToContractID map[string]types.FileContractID 99 100 // renewedFrom links the new contract's ID to the old contract's ID 101 // renewedTo links the old contract's ID to the new contract's ID 102 // doubleSpentContracts keep track of all contracts that were double spent by 103 // either the renter or host. 104 staticContracts *proto.ContractSet 105 oldContracts map[types.FileContractID]skymodules.RenterContract 106 preferredHosts map[string]struct{} 107 doubleSpentContracts map[types.FileContractID]types.BlockHeight 108 recoverableContracts map[types.FileContractID]skymodules.RecoverableContract 109 renewedFrom map[types.FileContractID]types.FileContractID 110 renewedTo map[types.FileContractID]types.FileContractID 111 112 staticChurnLimiter *churnLimiter 113 staticWatchdog *watchdog 114 } 115 116 // PaymentDetails is a helper struct that contains extra information on a 117 // payment. Most notably it includes a breakdown of the spending details for a 118 // payment, the contractor uses this information to update its spending details 119 // accordingly. 120 type PaymentDetails struct { 121 // destination details 122 Host types.SiaPublicKey 123 124 // payment details 125 Amount types.Currency 126 RefundAccount modules.AccountID 127 128 // spending details 129 SpendingDetails skymodules.SpendingDetails 130 } 131 132 // Allowance returns the current allowance. 133 func (c *Contractor) Allowance() skymodules.Allowance { 134 c.mu.RLock() 135 defer c.mu.RUnlock() 136 return c.allowance 137 } 138 139 // ContractPublicKey returns the public key capable of verifying the renter's 140 // signature on a contract. 141 func (c *Contractor) ContractPublicKey(pk types.SiaPublicKey) (crypto.PublicKey, bool) { 142 c.mu.RLock() 143 id, ok := c.pubKeysToContractID[pk.String()] 144 c.mu.RUnlock() 145 if !ok { 146 return crypto.PublicKey{}, false 147 } 148 return c.staticContracts.PublicKey(id) 149 } 150 151 // InitRecoveryScan starts scanning the whole blockchain for recoverable 152 // contracts within a separate thread. 153 func (c *Contractor) InitRecoveryScan() (err error) { 154 if err := c.staticTG.Add(); err != nil { 155 return err 156 } 157 defer c.staticTG.Done() 158 return c.callInitRecoveryScan(modules.ConsensusChangeBeginning) 159 } 160 161 // PeriodSpending returns the amount spent on contracts during the current 162 // billing period. 163 func (c *Contractor) PeriodSpending() (skymodules.ContractorSpending, error) { 164 allContracts := c.staticContracts.ViewAll() 165 c.mu.RLock() 166 defer c.mu.RUnlock() 167 168 var spending skymodules.ContractorSpending 169 for _, contract := range allContracts { 170 // Don't count double-spent contracts. 171 if _, doubleSpent := c.doubleSpentContracts[contract.ID]; doubleSpent { 172 continue 173 } 174 175 // Calculate ContractFees 176 spending.Fees.ContractFees = spending.Fees.ContractFees.Add(contract.ContractFee) 177 spending.Fees.SiafundFees = spending.Fees.SiafundFees.Add(contract.SiafundFee) 178 spending.Fees.TransactionFees = spending.Fees.TransactionFees.Add(contract.TxnFee) 179 // Calculate TotalAllocated 180 spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost) 181 spending.ContractSpendingDeprecated = spending.TotalAllocated 182 // Calculate Spending 183 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 184 spending.FundAccountSpending = spending.FundAccountSpending.Add(contract.FundAccountSpending) 185 spending.MaintenanceSpending = spending.MaintenanceSpending.Add(contract.MaintenanceSpending) 186 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 187 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 188 } 189 190 // Calculate needed spending to be reported from old contracts 191 for _, contract := range c.oldContracts { 192 // Don't count double-spent contracts. 193 if _, doubleSpent := c.doubleSpentContracts[contract.ID]; doubleSpent { 194 continue 195 } 196 197 host, exist, err := c.staticHDB.Host(contract.HostPublicKey) 198 if contract.StartHeight >= c.currentPeriod { 199 // Calculate spending from contracts that were renewed during the current period 200 // Calculate ContractFees 201 spending.Fees.ContractFees = spending.Fees.ContractFees.Add(contract.ContractFee) 202 spending.Fees.SiafundFees = spending.Fees.SiafundFees.Add(contract.TxnFee) 203 spending.Fees.TransactionFees = spending.Fees.TransactionFees.Add(contract.SiafundFee) 204 // Calculate TotalAllocated 205 spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost) 206 // Calculate Spending 207 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 208 spending.FundAccountSpending = spending.FundAccountSpending.Add(contract.FundAccountSpending) 209 spending.MaintenanceSpending = spending.MaintenanceSpending.Add(contract.MaintenanceSpending) 210 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 211 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 212 } else if err != nil && exist && contract.EndHeight+host.WindowSize+types.MaturityDelay > c.blockHeight { 213 // Calculate funds that are being withheld in contracts 214 spending.WithheldFunds = spending.WithheldFunds.Add(contract.RenterFunds) 215 // Record the largest window size for worst case when reporting the spending 216 if contract.EndHeight+host.WindowSize+types.MaturityDelay >= spending.ReleaseBlock { 217 spending.ReleaseBlock = contract.EndHeight + host.WindowSize + types.MaturityDelay 218 } 219 // Calculate Previous spending 220 spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee). 221 Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending).Add(contract.FundAccountSpending).Add(contract.MaintenanceSpending.Sum()) 222 } else { 223 // Calculate Previous spending 224 spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee). 225 Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending).Add(contract.FundAccountSpending).Add(contract.MaintenanceSpending.Sum()) 226 } 227 } 228 229 // Calculate amount of spent money to get unspent money. 230 allSpending := spending.Fees.Sum() 231 allSpending = allSpending.Add(spending.DownloadSpending) 232 allSpending = allSpending.Add(spending.UploadSpending) 233 allSpending = allSpending.Add(spending.StorageSpending) 234 allSpending = allSpending.Add(spending.FundAccountSpending) 235 allSpending = allSpending.Add(spending.MaintenanceSpending.Sum()) 236 if c.allowance.Funds.Cmp(allSpending) >= 0 { 237 spending.Unspent = c.allowance.Funds.Sub(allSpending) 238 } 239 240 return spending, nil 241 } 242 243 // CurrentPeriod returns the height at which the current allowance period 244 // began. 245 func (c *Contractor) CurrentPeriod() types.BlockHeight { 246 c.mu.RLock() 247 defer c.mu.RUnlock() 248 return c.currentPeriod 249 } 250 251 // UpdateWorkerPool updates the workerpool currently in use by the contractor. 252 func (c *Contractor) UpdateWorkerPool(wp skymodules.WorkerPool) { 253 c.mu.Lock() 254 defer c.mu.Unlock() 255 c.staticWorkerPool = wp 256 } 257 258 // ProvidePayment takes a stream and a set of payment details and handles 259 // the payment for an RPC by sending and processing payment request and 260 // response objects to the host. It returns an error in case of failure. 261 func (c *Contractor) ProvidePayment(stream io.ReadWriter, pt *modules.RPCPriceTable, details PaymentDetails) error { 262 // convenience variables 263 host := details.Host 264 refundAccount := details.RefundAccount 265 amount := details.Amount 266 bh := pt.HostBlockHeight 267 268 // find a contract for the given host 269 contract, exists := c.ContractByPublicKey(host) 270 if !exists { 271 return errContractNotFound 272 } 273 274 // acquire a safe contract 275 sc, exists := c.staticContracts.Acquire(contract.ID) 276 if !exists { 277 return errContractNotFound 278 } 279 defer c.staticContracts.Return(sc) 280 281 // create a new revision 282 current := sc.LastRevision() 283 rev, err := current.EAFundRevision(amount) 284 if err != nil { 285 return errors.AddContext(err, "Failed to create a payment revision") 286 } 287 288 // create transaction containing the revision 289 signedTxn := rev.ToTransaction() 290 sig := sc.Sign(signedTxn.SigHash(0, bh)) 291 signedTxn.TransactionSignatures[0].Signature = sig[:] 292 293 // record the payment intent 294 walTxn, err := sc.RecordPaymentIntent(rev, amount, details.SpendingDetails) 295 if err != nil { 296 return errors.AddContext(err, "Failed to record payment intent") 297 } 298 299 // prepare a buffer so we can optimize our writes 300 buffer := bytes.NewBuffer(nil) 301 302 // send PaymentRequest 303 err = modules.RPCWrite(buffer, modules.PaymentRequest{Type: modules.PayByContract}) 304 if err != nil { 305 return errors.AddContext(err, "unable to write payment request to host") 306 } 307 308 // send PayByContractRequest 309 err = modules.RPCWrite(buffer, newPayByContractRequest(rev, sig, refundAccount)) 310 if err != nil { 311 return errors.AddContext(err, "unable to write the pay by contract request") 312 } 313 314 // write contents of the buffer to the stream 315 _, err = stream.Write(buffer.Bytes()) 316 if err != nil { 317 return errors.AddContext(err, "could not write the buffer contents") 318 } 319 320 // receive PayByContractResponse 321 var payByResponse modules.PayByContractResponse 322 if err := modules.RPCRead(stream, &payByResponse); err != nil { 323 if strings.Contains(err.Error(), "storage obligation not found") { 324 c.staticLog.Printf("Marking contract %v as bad because host %v did not recognize it: %v", contract.ID, host, err) 325 mbcErr := c.managedMarkContractBad(sc) 326 if mbcErr != nil { 327 c.staticLog.Printf("Unable to mark contract %v on host %v as bad: %v", contract.ID, host, mbcErr) 328 } 329 } 330 return errors.AddContext(err, "unable to read the pay by contract response") 331 } 332 333 // TODO: Check for revision mismatch and recover by applying the contract 334 // unapplied transactions and trying again. 335 336 // verify the host's signature 337 hash := crypto.HashAll(rev) 338 hpk := sc.Metadata().HostPublicKey 339 err = crypto.VerifyHash(hash, hpk.ToPublicKey(), payByResponse.Signature) 340 if err != nil { 341 return errors.New("could not verify host's signature") 342 } 343 344 // commit payment intent 345 if !c.staticDeps.Disrupt("DisableCommitPaymentIntent") { 346 err = sc.CommitPaymentIntent(walTxn, signedTxn, amount, details.SpendingDetails) 347 if err != nil { 348 return errors.AddContext(err, "Failed to commit unknown spending intent") 349 } 350 } 351 352 return nil 353 } 354 355 // RecoveryScanStatus returns a bool indicating if a scan for recoverable 356 // contracts is in progress and if it is, the current progress of the scan. 357 func (c *Contractor) RecoveryScanStatus() (bool, types.BlockHeight) { 358 bh := types.BlockHeight(atomic.LoadInt64(&c.atomicRecoveryScanHeight)) 359 sip := atomic.LoadUint32(&c.atomicScanInProgress) 360 return sip == 1, bh 361 } 362 363 // RefreshedContract returns a bool indicating if the contract was a refreshed 364 // contract. A refreshed contract refers to a contract that ran out of funds 365 // prior to the end height and so was renewed with the host in the same period. 366 // Both the old and the new contract have the same end height 367 func (c *Contractor) RefreshedContract(fcid types.FileContractID) bool { 368 // Add thread and acquire lock 369 if err := c.staticTG.Add(); err != nil { 370 return false 371 } 372 defer c.staticTG.Done() 373 c.mu.RLock() 374 defer c.mu.RUnlock() 375 376 // Check if contract ID is found in the renewedTo map indicating that the 377 // contract was renewed 378 newFCID, renewed := c.renewedTo[fcid] 379 if !renewed { 380 return false 381 } 382 383 // Grab the contract to check its end height 384 contract, ok := c.oldContracts[fcid] 385 if !ok { 386 c.staticLog.Debugln("Contract not found in oldContracts, despite there being a renewal to the contract") 387 return false 388 } 389 390 // Grab the contract it was renewed to to check its end height 391 newContract, ok := c.staticContracts.View(newFCID) 392 if !ok { 393 newContract, ok = c.oldContracts[newFCID] 394 if !ok { 395 c.staticLog.Debugln("Contract was not found in the database, despite their being another contract that claims to have renewed to it.") 396 return false 397 } 398 } 399 400 // The contract was refreshed if the endheights are the same 401 return newContract.EndHeight == contract.EndHeight 402 } 403 404 // Synced returns a channel that is closed when the contractor is synced with 405 // the peer-to-peer network. 406 func (c *Contractor) Synced() <-chan struct{} { 407 c.mu.RLock() 408 defer c.mu.RUnlock() 409 return c.synced 410 } 411 412 // Close closes the Contractor. 413 func (c *Contractor) Close() error { 414 return c.staticTG.Stop() 415 } 416 417 // newWithDeps returns a new Contractor. 418 func newWithDeps(cs modules.ConsensusSet, wallet modules.Wallet, tpool modules.TransactionPool, hdb skymodules.HostDB, rl *ratelimit.RateLimit, persistDir string, deps modules.Dependencies) (*Contractor, <-chan error) { 419 errChan := make(chan error, 1) 420 defer close(errChan) 421 // Check for nil inputs. 422 if cs == nil { 423 errChan <- errNilCS 424 return nil, errChan 425 } 426 if wallet == nil { 427 errChan <- errNilWallet 428 return nil, errChan 429 } 430 if tpool == nil { 431 errChan <- errNilTpool 432 return nil, errChan 433 } 434 if hdb == nil { 435 errChan <- errNilHDB 436 return nil, errChan 437 } 438 439 // Create the persist directory if it does not yet exist. 440 if err := os.MkdirAll(persistDir, 0700); err != nil { 441 errChan <- err 442 return nil, errChan 443 } 444 445 // Convert the old persist file(s), if necessary. This must occur before 446 // loading the contract set. 447 if err := convertPersist(persistDir, rl); err != nil { 448 errChan <- err 449 return nil, errChan 450 } 451 452 // Create the contract set. 453 contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), rl, modules.ProdDependencies) 454 if err != nil { 455 errChan <- err 456 return nil, errChan 457 } 458 // Create the logger. 459 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log")) 460 if err != nil { 461 errChan <- err 462 return nil, errChan 463 } 464 465 // Create Contractor using production dependencies. 466 return NewCustomContractor(cs, wallet, tpool, hdb, persistDir, contractSet, logger, deps) 467 } 468 469 // New returns a new Contractor. 470 func New(cs modules.ConsensusSet, wallet modules.Wallet, tpool modules.TransactionPool, hdb skymodules.HostDB, rl *ratelimit.RateLimit, persistDir string) (*Contractor, <-chan error) { 471 return newWithDeps(cs, wallet, tpool, hdb, rl, persistDir, modules.ProdDependencies) 472 } 473 474 // contractorBlockingStartup handles the blocking portion of NewCustomContractor. 475 func contractorBlockingStartup(cs modules.ConsensusSet, w modules.Wallet, tp modules.TransactionPool, hdb skymodules.HostDB, persistDir string, contractSet *proto.ContractSet, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) { 476 // Create the Contractor object. 477 c := &Contractor{ 478 staticAlerter: skymodules.NewAlerter("contractor"), 479 staticCS: cs, 480 staticDeps: deps, 481 staticHDB: hdb, 482 staticLog: l, 483 persistDir: persistDir, 484 staticTPool: tp, 485 staticWallet: w, 486 487 staticInterruptMaintenance: make(chan struct{}), 488 synced: make(chan struct{}), 489 490 staticContracts: contractSet, 491 downloaders: make(map[types.FileContractID]*hostDownloader), 492 editors: make(map[types.FileContractID]*hostEditor), 493 sessions: make(map[types.FileContractID]*hostSession), 494 oldContracts: make(map[types.FileContractID]skymodules.RenterContract), 495 doubleSpentContracts: make(map[types.FileContractID]types.BlockHeight), 496 preferredHosts: make(map[string]struct{}), 497 recoverableContracts: make(map[types.FileContractID]skymodules.RecoverableContract), 498 renewing: make(map[types.FileContractID]bool), 499 renewedFrom: make(map[types.FileContractID]types.FileContractID), 500 renewedTo: make(map[types.FileContractID]types.FileContractID), 501 staticWorkerPool: emptyWorkerPool{}, 502 } 503 c.staticChurnLimiter = newChurnLimiter(c) 504 c.staticWatchdog = newWatchdog(c) 505 506 // Close the contract set and logger upon shutdown. 507 err := c.staticTG.AfterStop(func() error { 508 if err := c.staticContracts.Close(); err != nil { 509 return errors.AddContext(err, "failed to close contract set") 510 } 511 if err := c.staticLog.Close(); err != nil { 512 return errors.AddContext(err, "failed to close the contractor logger") 513 } 514 return nil 515 }) 516 if err != nil { 517 return nil, err 518 } 519 520 // Load the prior persistence structures. 521 err = c.load() 522 if err != nil && !os.IsNotExist(err) { 523 return nil, err 524 } 525 526 // Update the pubkeyToContractID map 527 c.managedUpdatePubKeyToContractIDMap() 528 529 // Unsubscribe from the consensus set upon shutdown. 530 err = c.staticTG.OnStop(func() error { 531 cs.Unsubscribe(c) 532 return nil 533 }) 534 if err != nil { 535 return nil, err 536 } 537 538 // We may have upgraded persist or resubscribed. Save now so that we don't 539 // lose our work. 540 c.mu.Lock() 541 err = c.save() 542 c.mu.Unlock() 543 if err != nil { 544 return nil, err 545 } 546 547 // Update the allowance in the hostdb with the one that was loaded from 548 // disk. 549 err = c.staticHDB.SetAllowance(c.allowance) 550 if err != nil { 551 return nil, err 552 } 553 return c, nil 554 } 555 556 // contractorAsyncStartup handles the async portion of NewCustomContractor. 557 func contractorAsyncStartup(c *Contractor, cs modules.ConsensusSet) error { 558 if c.staticDeps.Disrupt("BlockAsyncStartup") { 559 return nil 560 } 561 err := cs.ConsensusSetSubscribe(c, c.lastChange, c.staticTG.StopChan()) 562 if errors.Contains(err, modules.ErrInvalidConsensusChangeID) { 563 // Reset the contractor consensus variables and try rescanning. 564 c.blockHeight = 0 565 c.lastChange = modules.ConsensusChangeBeginning 566 c.recentRecoveryChange = modules.ConsensusChangeBeginning 567 err = cs.ConsensusSetSubscribe(c, c.lastChange, c.staticTG.StopChan()) 568 } 569 if err != nil && strings.Contains(err.Error(), threadgroup.ErrStopped.Error()) { 570 return nil 571 } 572 if err != nil { 573 return err 574 } 575 return nil 576 } 577 578 // NewCustomContractor creates a Contractor using the provided dependencies. 579 func NewCustomContractor(cs modules.ConsensusSet, w modules.Wallet, tp modules.TransactionPool, hdb skymodules.HostDB, persistDir string, contractSet *proto.ContractSet, l *persist.Logger, deps modules.Dependencies) (*Contractor, <-chan error) { 580 errChan := make(chan error, 1) 581 582 // Handle blocking startup. 583 c, err := contractorBlockingStartup(cs, w, tp, hdb, persistDir, contractSet, l, deps) 584 if err != nil { 585 errChan <- err 586 return nil, errChan 587 } 588 589 // non-blocking startup. 590 go func() { 591 // Subscribe to the consensus set in a separate goroutine. 592 defer close(errChan) 593 if err := c.staticTG.Add(); err != nil { 594 errChan <- err 595 return 596 } 597 defer c.staticTG.Done() 598 err := contractorAsyncStartup(c, cs) 599 if err != nil { 600 errChan <- err 601 } 602 }() 603 return c, errChan 604 } 605 606 // callInitRecoveryScan starts scanning the whole blockchain at a certain 607 // ChangeID for recoverable contracts within a separate thread. 608 func (c *Contractor) callInitRecoveryScan(scanStart modules.ConsensusChangeID) (err error) { 609 // Check if we are already scanning the blockchain. 610 if !atomic.CompareAndSwapUint32(&c.atomicScanInProgress, 0, 1) { 611 return errors.New("scan for recoverable contracts is already in progress") 612 } 613 // Reset the progress and status if there was an error. 614 defer func() { 615 if err != nil { 616 atomic.StoreUint32(&c.atomicScanInProgress, 0) 617 atomic.StoreInt64(&c.atomicRecoveryScanHeight, 0) 618 } 619 }() 620 // Get the wallet seed. 621 s, _, err := c.staticWallet.PrimarySeed() 622 if err != nil { 623 return errors.AddContext(err, "failed to get wallet seed") 624 } 625 // Get the renter seed and wipe it once done. 626 rs := skymodules.DeriveRenterSeed(s) 627 // Reset the scan progress before starting the scan. 628 atomic.StoreInt64(&c.atomicRecoveryScanHeight, 0) 629 // Create the scanner. 630 scanner := newRecoveryScanner(c, rs) 631 // Start the scan. 632 go func() { 633 // Add scanning thread to threadgroup. 634 if err := c.staticTG.Add(); err != nil { 635 return 636 } 637 defer c.staticTG.Done() 638 // Scan blockchain. 639 if err := scanner.threadedScan(c.staticCS, scanStart, c.staticTG.StopChan()); err != nil { 640 c.staticLog.Println("Scan failed", err) 641 } 642 if c.staticDeps.Disrupt("disableRecoveryStatusReset") { 643 return 644 } 645 // Reset the scan related fields. 646 if !atomic.CompareAndSwapUint32(&c.atomicScanInProgress, 1, 0) { 647 build.Critical("finished recovery scan but scanInProgress was already set to 0") 648 } 649 atomic.StoreInt64(&c.atomicRecoveryScanHeight, 0) 650 // Save the renter. 651 c.mu.Lock() 652 c.save() 653 c.mu.Unlock() 654 }() 655 return nil 656 } 657 658 // managedSynced returns true if the contractor is synced with the consensusset. 659 func (c *Contractor) managedSynced() bool { 660 c.mu.RLock() 661 defer c.mu.RUnlock() 662 select { 663 case <-c.synced: 664 return true 665 default: 666 } 667 return false 668 } 669 670 // newPayByContractRequest is a helper function that takes a revision, 671 // signature and refund account and creates a PayByContractRequest object. 672 func newPayByContractRequest(rev types.FileContractRevision, sig crypto.Signature, refundAccount modules.AccountID) modules.PayByContractRequest { 673 req := modules.PayByContractRequest{ 674 ContractID: rev.ID(), 675 NewRevisionNumber: rev.NewRevisionNumber, 676 NewValidProofValues: make([]types.Currency, len(rev.NewValidProofOutputs)), 677 NewMissedProofValues: make([]types.Currency, len(rev.NewMissedProofOutputs)), 678 RefundAccount: refundAccount, 679 Signature: sig[:], 680 } 681 for i, o := range rev.NewValidProofOutputs { 682 req.NewValidProofValues[i] = o.Value 683 } 684 for i, o := range rev.NewMissedProofOutputs { 685 req.NewMissedProofValues[i] = o.Value 686 } 687 return req 688 } 689 690 // RenewContract takes an established connection to a host and renews the 691 // contract with that host. 692 func (c *Contractor) RenewContract(conn net.Conn, fcid types.FileContractID, params skymodules.ContractParams, txnBuilder modules.TransactionBuilder, tpool modules.TransactionPool, hdb skymodules.HostDB, pt *modules.RPCPriceTable) (skymodules.RenterContract, []types.Transaction, error) { 693 newContract, txnSet, err := c.staticContracts.RenewContract(conn, fcid, params, txnBuilder, tpool, hdb, pt) 694 if err != nil { 695 return skymodules.RenterContract{}, nil, errors.AddContext(err, "RenewContract: failed to renew contract") 696 } 697 // Update various mappings in the contractor after a successful renewal. 698 c.mu.Lock() 699 c.renewedFrom[newContract.ID] = fcid 700 c.renewedTo[fcid] = newContract.ID 701 c.pubKeysToContractID[newContract.HostPublicKey.String()] = newContract.ID 702 c.mu.Unlock() 703 return newContract, txnSet, nil 704 } 705 706 // ResetContractUtilities will reset all bad and locked contracts, as well 707 // as trigger a contract maintenance. 708 func (c *Contractor) ResetContractUtilities() error { 709 // add ourselves to the threadgroup 710 err := c.staticTG.Add() 711 if err != nil { 712 return err 713 } 714 defer c.staticTG.Done() 715 716 // reset contract utilities 717 err = c.managedResetContractUtilities() 718 if err != nil { 719 return err 720 } 721 722 // schedule maintenance 723 go c.threadedContractMaintenance() 724 725 return nil 726 } 727 728 // managedResetContractUtilities will reset all bad and locked contracts, as well 729 // as trigger a contract maintenance. 730 func (c *Contractor) managedResetContractUtilities() error { 731 // reset the failed renews map 732 c.managedResetFailedRenews() 733 734 // reset all cancelled contracts 735 var errs []error 736 for _, rc := range c.staticContracts.ViewAll() { 737 if rc.Utility.BadContract || rc.Utility.Locked { 738 errs = append(errs, errors.AddContext(c.managedResetContract(rc.ID), fmt.Sprintf("failed to reset contract %v", rc.ID))) 739 } 740 } 741 return errors.Compose(errs...) 742 } 743 744 // managedResetFailedRenews resets the failed renews map on the contractor 745 func (c *Contractor) managedResetFailedRenews() { 746 c.mu.Lock() 747 defer c.mu.Unlock() 748 c.numFailedRenews = make(map[types.FileContractID]types.BlockHeight) 749 }