gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/update.go (about)

     1  package contractor
     2  
     3  import (
     4  	"gitlab.com/NebulousLabs/fastrand"
     5  
     6  	"gitlab.com/SkynetLabs/skyd/build"
     7  	"gitlab.com/SkynetLabs/skyd/skymodules"
     8  	"go.sia.tech/siad/crypto"
     9  	"go.sia.tech/siad/modules"
    10  	"go.sia.tech/siad/types"
    11  )
    12  
    13  // hasFCIdentifier checks the transaction for a ContractSignedIdentifier and
    14  // returns the first one it finds with a bool indicating if an identifier was
    15  // found.
    16  func hasFCIdentifier(txn types.Transaction) (skymodules.ContractSignedIdentifier, crypto.Ciphertext, bool) {
    17  	// We don't verify the host key here so we only need to make sure the
    18  	// identifier fits into the arbitrary data.
    19  	if len(txn.ArbitraryData) != 1 || len(txn.ArbitraryData[0]) < skymodules.FCSignedIdentiferSize {
    20  		return skymodules.ContractSignedIdentifier{}, nil, false
    21  	}
    22  	// Verify the prefix.
    23  	var prefix types.Specifier
    24  	copy(prefix[:], txn.ArbitraryData[0])
    25  	if prefix != modules.PrefixFileContractIdentifier &&
    26  		prefix != modules.PrefixNonSia {
    27  		return skymodules.ContractSignedIdentifier{}, nil, false
    28  	}
    29  	// We found an identifier.
    30  	var csi skymodules.ContractSignedIdentifier
    31  	n := copy(csi[:], txn.ArbitraryData[0])
    32  	hostKey := txn.ArbitraryData[0][n:]
    33  	return csi, hostKey, true
    34  }
    35  
    36  // HostRevisionSubmissionBuffer describes the number of blocks ahead of time
    37  // that the host will submit a file contract revision. The host will not
    38  // accept any more revisions once inside the submission buffer.
    39  var HostRevisionSubmissionBuffer = build.Select(build.Var{
    40  	Dev:      types.BlockHeight(20),  // About 4 minutes
    41  	Standard: types.BlockHeight(144), // 1 day.
    42  	Testing:  types.BlockHeight(4),
    43  }).(types.BlockHeight)
    44  
    45  // contractsToDelete returns how many contracts of numContracts potential
    46  // candidates should be deleted within managedArchiveContracts. It returns a
    47  // number between 0 and 1% of numContracts.
    48  // 1% was chosen since we want to delete all contracts eventually within 1 day
    49  // and scales well for larger numbers of contracts since we will delete more
    50  // contracts at first and fewer ones in subsequent calls.
    51  func contractsToDelete(numContracts int) int {
    52  	toDelete := int(float64(numContracts) * 0.01)
    53  	if numContracts > 0 && toDelete == 0 {
    54  		return 1 // delete at least 1
    55  	}
    56  	return toDelete
    57  }
    58  
    59  // managedArchiveContracts will figure out which contracts are no longer needed
    60  // and move them to the historic set of contracts.
    61  func (c *Contractor) managedArchiveContracts() {
    62  	// Determine the current block height.
    63  	c.mu.RLock()
    64  	currentHeight := c.blockHeight
    65  	c.mu.RUnlock()
    66  
    67  	// Loop through the current set of contracts and migrate any expired ones to
    68  	// the set of old contracts.
    69  	var expired []types.FileContractID
    70  	var potentialDeletes []skymodules.RenterContract
    71  	for _, contract := range c.staticContracts.ViewAll() {
    72  		// Check map of renewedTo in case renew code was interrupted before
    73  		// archiving old contract
    74  		c.mu.RLock()
    75  		_, renewed := c.renewedTo[contract.ID]
    76  		c.mu.RUnlock()
    77  		if currentHeight+HostRevisionSubmissionBuffer > contract.EndHeight || renewed {
    78  			id := contract.ID
    79  			c.mu.Lock()
    80  			c.oldContracts[id] = contract
    81  			c.mu.Unlock()
    82  			expired = append(expired, id)
    83  			c.staticLog.Println("INFO: archived renewed contract", id)
    84  		} else if !c.staticDeps.Disrupt("DisableEarlyContractArchival") && currentHeight+2*HostRevisionSubmissionBuffer > contract.EndHeight {
    85  			potentialDeletes = append(potentialDeletes, contract)
    86  		}
    87  	}
    88  
    89  	// Delete a part of the potential candidates for deletion.
    90  	toDelete := contractsToDelete(len(potentialDeletes))
    91  	for _, contract := range potentialDeletes[:toDelete] {
    92  		id := contract.ID
    93  		c.mu.Lock()
    94  		c.oldContracts[id] = contract
    95  		c.mu.Unlock()
    96  		expired = append(expired, id)
    97  		c.staticLog.Println("INFO: archived expired contract", id)
    98  	}
    99  
   100  	// Save.
   101  	c.mu.Lock()
   102  	c.save()
   103  	c.mu.Unlock()
   104  
   105  	// Delete all the expired contracts from the contract set.
   106  	for _, id := range expired {
   107  		if sc, ok := c.staticContracts.Acquire(id); ok {
   108  			c.staticContracts.Delete(sc)
   109  		}
   110  	}
   111  }
   112  
   113  // ProcessConsensusChange will be called by the consensus set every time there
   114  // is a change in the blockchain. Updates will always be called in order.
   115  func (c *Contractor) ProcessConsensusChange(cc modules.ConsensusChange) {
   116  	// Get the wallet's seed for contract recovery.
   117  	haveSeed := true
   118  	missedRecovery := false
   119  	s, _, err := c.staticWallet.PrimarySeed()
   120  	if err != nil {
   121  		haveSeed = false
   122  	}
   123  	// Get the master renter seed and wipe it once we are done with it.
   124  	var renterSeed skymodules.RenterSeed
   125  	if haveSeed {
   126  		renterSeed = skymodules.DeriveRenterSeed(s)
   127  		defer fastrand.Read(renterSeed[:])
   128  	}
   129  
   130  	c.mu.Lock()
   131  	for _, block := range cc.RevertedBlocks {
   132  		if block.ID() != types.GenesisID {
   133  			c.blockHeight--
   134  		}
   135  		// Remove recoverable contracts found in reverted block.
   136  		c.removeRecoverableContracts(block)
   137  	}
   138  	for _, block := range cc.AppliedBlocks {
   139  		if block.ID() != types.GenesisID {
   140  			c.blockHeight++
   141  		}
   142  		// Find lost contracts for recovery.
   143  		if haveSeed {
   144  			c.findRecoverableContracts(renterSeed, block)
   145  		} else {
   146  			missedRecovery = true
   147  		}
   148  	}
   149  	c.staticWatchdog.callScanConsensusChange(cc)
   150  
   151  	// If we didn't miss the recover, we update the recentRecoverChange
   152  	if !missedRecovery && c.recentRecoveryChange == c.lastChange {
   153  		c.recentRecoveryChange = cc.ID
   154  	}
   155  
   156  	// If the allowance is set and we have entered the next period, update
   157  	// currentPeriod.
   158  	if c.allowance.Active() && c.blockHeight >= c.currentPeriod+c.allowance.Period {
   159  		c.currentPeriod += c.allowance.Period
   160  		c.staticChurnLimiter.callResetAggregateChurn()
   161  
   162  		// COMPATv1.0.4-lts
   163  		// if we were storing a special metrics contract, it will be invalid
   164  		// after we enter the next period.
   165  		delete(c.oldContracts, metricsContractID)
   166  	}
   167  
   168  	// Check if c.synced already signals that the contractor is synced.
   169  	synced := false
   170  	select {
   171  	case <-c.synced:
   172  		synced = true
   173  	default:
   174  	}
   175  	// If we weren't synced but are now, we close the channel. If we were
   176  	// synced but aren't anymore, we need a new channel.
   177  	if !synced && cc.Synced {
   178  		close(c.synced)
   179  	} else if synced && !cc.Synced {
   180  		c.synced = make(chan struct{})
   181  	}
   182  	// Let the watchdog take any necessary actions and update its state. We do
   183  	// this before persisting the contractor so that the watchdog is up-to-date on
   184  	// reboot. Otherwise it is possible that e.g. that the watchdog thinks a
   185  	// storage proof was missed and marks down a host for that. Other watchdog
   186  	// actions are innocuous.
   187  	if cc.Synced {
   188  		c.staticWatchdog.callCheckContracts()
   189  	}
   190  
   191  	c.lastChange = cc.ID
   192  	err = c.save()
   193  	if err != nil {
   194  		c.staticLog.Println("Unable to save while processing a consensus change:", err)
   195  	}
   196  	a := c.allowance
   197  	c.mu.Unlock()
   198  
   199  	// Add to churnLimiter budget.
   200  	numBlocksAdded := len(cc.AppliedBlocks) - len(cc.RevertedBlocks)
   201  	c.staticChurnLimiter.callBumpChurnBudget(numBlocksAdded, a.Period)
   202  
   203  	// Perform contract maintenance if our blockchain is synced. Use a separate
   204  	// goroutine so that the rest of the contractor is not blocked during
   205  	// maintenance.
   206  	if cc.Synced {
   207  		go c.threadedContractMaintenance()
   208  	}
   209  }