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 }