gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/update.go (about) 1 package host 2 3 // TODO: Need to check that 'RevisionConfirmed' is sensitive to whether or not 4 // it was the *most recent* revision that got confirmed. 5 6 import ( 7 "encoding/binary" 8 "encoding/json" 9 10 bolt "github.com/coreos/bbolt" 11 "gitlab.com/SiaPrime/SiaPrime/crypto" 12 "gitlab.com/SiaPrime/SiaPrime/modules" 13 "gitlab.com/SiaPrime/SiaPrime/types" 14 ) 15 16 // initRescan is a helper function of initConsensusSubscribe, and is called when 17 // the host and the consensus set have become desynchronized. Desynchronization 18 // typically happens if the user is replacing or altering the persistent files 19 // in the consensus set or the host. 20 func (h *Host) initRescan() error { 21 // Reset all of the variables that have relevance to the consensus set. 22 var allObligations []storageObligation 23 // Reset all of the consensus-relevant variables in the host. 24 h.blockHeight = 0 25 26 // Reset all of the storage obligations. 27 err := h.db.Update(func(tx *bolt.Tx) error { 28 bsu := tx.Bucket(bucketStorageObligations) 29 c := bsu.Cursor() 30 for k, soBytes := c.First(); soBytes != nil; k, soBytes = c.Next() { 31 var so storageObligation 32 err := json.Unmarshal(soBytes, &so) 33 if err != nil { 34 return err 35 } 36 so.OriginConfirmed = false 37 so.RevisionConfirmed = false 38 so.ProofConfirmed = false 39 allObligations = append(allObligations, so) 40 soBytes, err = json.Marshal(so) 41 if err != nil { 42 return err 43 } 44 err = bsu.Put(k, soBytes) 45 if err != nil { 46 return err 47 } 48 } 49 return nil 50 }) 51 if err != nil { 52 return err 53 } 54 55 // Subscribe to the consensus set. This is a blocking call that will not 56 // return until the host has fully caught up to the current block. 57 // 58 // Convention dictates that the host should not make external calls while 59 // under lock, but this function happens at startup while blocking. Because 60 // it happens while blocking, and because there is no actual host lock held 61 // at this time, none of the host external functions are exposed, so it is 62 // save to make the exported call. 63 err = h.cs.ConsensusSetSubscribe(h, modules.ConsensusChangeBeginning, h.tg.StopChan()) 64 if err != nil { 65 return err 66 } 67 h.tg.OnStop(func() { 68 h.cs.Unsubscribe(h) 69 }) 70 71 // Re-queue all of the action items for the storage obligations. 72 for i, so := range allObligations { 73 soid := so.id() 74 err1 := h.queueActionItem(h.blockHeight+resubmissionTimeout, soid) 75 err2 := h.queueActionItem(so.expiration()-revisionSubmissionBuffer, soid) 76 err3 := h.queueActionItem(so.expiration()+resubmissionTimeout, soid) 77 err = composeErrors(err1, err2, err3) 78 if err != nil { 79 h.log.Println("dropping storage obligation during rescan, id", so.id()) 80 } 81 82 // AcceptTransactionSet needs to be called in a goroutine to avoid a 83 // deadlock. 84 go func(i int) { 85 err := h.tpool.AcceptTransactionSet(allObligations[i].OriginTransactionSet) 86 if err != nil { 87 h.log.Println("Unable to submit contract transaction set after rescan:", soid) 88 } 89 }(i) 90 } 91 return nil 92 } 93 94 // initConsensusSubscription subscribes the host to the consensus set. 95 func (h *Host) initConsensusSubscription() error { 96 // Convention dictates that the host should not make external calls while 97 // under lock, but this function happens at startup while blocking. Because 98 // it happens while blocking, and because there is no actual host lock held 99 // at this time, none of the host external functions are exposed, so it is 100 // save to make the exported call. 101 err := h.cs.ConsensusSetSubscribe(h, h.recentChange, h.tg.StopChan()) 102 if err == modules.ErrInvalidConsensusChangeID { 103 // Perform a rescan of the consensus set if the change id that the host 104 // has is unrecognized by the consensus set. This will typically only 105 // happen if the user has been replacing files inside the Sia folder 106 // structure. 107 return h.initRescan() 108 } 109 if err != nil { 110 return err 111 } 112 h.tg.OnStop(func() { 113 h.cs.Unsubscribe(h) 114 }) 115 return nil 116 } 117 118 // ProcessConsensusChange will be called by the consensus set every time there 119 // is a change to the blockchain. 120 func (h *Host) ProcessConsensusChange(cc modules.ConsensusChange) { 121 // Add is called at the beginning of the function, but Done cannot be 122 // called until all of the threads spawned by this function have also 123 // terminated. This function should not block while these threads wait to 124 // terminate. 125 h.mu.Lock() 126 defer h.mu.Unlock() 127 128 // Wrap the whole parsing into a single large database tx to keep things 129 // efficient. 130 var actionItems []types.FileContractID 131 err := h.db.Update(func(tx *bolt.Tx) error { 132 for _, block := range cc.RevertedBlocks { 133 // Look for transactions relevant to open storage obligations. 134 for _, txn := range block.Transactions { 135 // Check for file contracts. 136 if len(txn.FileContracts) > 0 { 137 for j := range txn.FileContracts { 138 fcid := txn.FileContractID(uint64(j)) 139 so, err := getStorageObligation(tx, fcid) 140 if err != nil { 141 // The storage folder may not exist, or the disk 142 // may be having trouble. Either way, we ignore the 143 // problem. If the disk is having trouble, the user 144 // will have to perform a rescan. 145 continue 146 } 147 so.OriginConfirmed = false 148 err = putStorageObligation(tx, so) 149 if err != nil { 150 continue 151 } 152 } 153 } 154 155 // Check for file contract revisions. 156 if len(txn.FileContractRevisions) > 0 { 157 for _, fcr := range txn.FileContractRevisions { 158 so, err := getStorageObligation(tx, fcr.ParentID) 159 if err != nil { 160 // The storage folder may not exist, or the disk 161 // may be having trouble. Either way, we ignore the 162 // problem. If the disk is having trouble, the user 163 // will have to perform a rescan. 164 continue 165 } 166 so.RevisionConfirmed = false 167 err = putStorageObligation(tx, so) 168 if err != nil { 169 continue 170 } 171 } 172 } 173 174 // Check for storage proofs. 175 if len(txn.StorageProofs) > 0 { 176 for _, sp := range txn.StorageProofs { 177 // Check database for relevant storage proofs. 178 so, err := getStorageObligation(tx, sp.ParentID) 179 if err != nil { 180 // The storage folder may not exist, or the disk 181 // may be having trouble. Either way, we ignore the 182 // problem. If the disk is having trouble, the user 183 // will have to perform a rescan. 184 continue 185 } 186 so.ProofConfirmed = false 187 err = putStorageObligation(tx, so) 188 if err != nil { 189 continue 190 } 191 } 192 } 193 } 194 195 // Height is not adjusted when dealing with the genesis block because 196 // the default height is 0 and the genesis block height is 0. If 197 // removing the genesis block, height will already be at height 0 and 198 // should not update, lest an underflow occur. 199 if block.ID() != types.GenesisID { 200 h.blockHeight-- 201 } 202 } 203 for _, block := range cc.AppliedBlocks { 204 // Look for transactions relevant to open storage obligations. 205 for _, txn := range block.Transactions { 206 // Check for file contracts. 207 if len(txn.FileContracts) > 0 { 208 for i := range txn.FileContracts { 209 fcid := txn.FileContractID(uint64(i)) 210 so, err := getStorageObligation(tx, fcid) 211 if err != nil { 212 // The storage folder may not exist, or the disk 213 // may be having trouble. Either way, we ignore the 214 // problem. If the disk is having trouble, the user 215 // will have to perform a rescan. 216 continue 217 } 218 so.OriginConfirmed = true 219 err = putStorageObligation(tx, so) 220 if err != nil { 221 continue 222 } 223 } 224 } 225 226 // Check for file contract revisions. 227 if len(txn.FileContractRevisions) > 0 { 228 for _, fcr := range txn.FileContractRevisions { 229 so, err := getStorageObligation(tx, fcr.ParentID) 230 if err != nil { 231 // The storage folder may not exist, or the disk 232 // may be having trouble. Either way, we ignore the 233 // problem. If the disk is having trouble, the user 234 // will have to perform a rescan. 235 continue 236 } 237 so.RevisionConfirmed = true 238 err = putStorageObligation(tx, so) 239 if err != nil { 240 continue 241 } 242 } 243 } 244 245 // Check for storage proofs. 246 if len(txn.StorageProofs) > 0 { 247 for _, sp := range txn.StorageProofs { 248 so, err := getStorageObligation(tx, sp.ParentID) 249 if err != nil { 250 // The storage folder may not exist, or the disk 251 // may be having trouble. Either way, we ignore the 252 // problem. If the disk is having trouble, the user 253 // will have to perform a rescan. 254 continue 255 } 256 so.ProofConfirmed = true 257 err = putStorageObligation(tx, so) 258 if err != nil { 259 continue 260 } 261 } 262 } 263 } 264 265 // Height is not adjusted when dealing with the genesis block because 266 // the default height is 0 and the genesis block height is 0. If adding 267 // the genesis block, height will already be at height 0 and should not 268 // update. 269 if block.ID() != types.GenesisID { 270 h.blockHeight++ 271 } 272 273 // Handle any action items relevant to the current height. 274 bai := tx.Bucket(bucketActionItems) 275 heightBytes := make([]byte, 8) 276 binary.BigEndian.PutUint64(heightBytes, uint64(h.blockHeight)) // BigEndian used so bolt will keep things sorted automatically. 277 existingItems := bai.Get(heightBytes) 278 279 // From the existing items, pull out a storage obligation. 280 knownActionItems := make(map[types.FileContractID]struct{}) 281 obligationIDs := make([]types.FileContractID, len(existingItems)/crypto.HashSize) 282 for i := 0; i < len(existingItems); i += crypto.HashSize { 283 copy(obligationIDs[i/crypto.HashSize][:], existingItems[i:i+crypto.HashSize]) 284 } 285 for _, soid := range obligationIDs { 286 _, exists := knownActionItems[soid] 287 if !exists { 288 actionItems = append(actionItems, soid) 289 knownActionItems[soid] = struct{}{} 290 } 291 } 292 } 293 return nil 294 }) 295 if err != nil { 296 h.log.Println(err) 297 } 298 for i := range actionItems { 299 go h.threadedHandleActionItem(actionItems[i]) 300 } 301 302 // Update the host's recent change pointer to point to the most recent 303 // change. 304 h.recentChange = cc.ID 305 306 // Save the host. 307 err = h.saveSync() 308 if err != nil { 309 h.log.Println("ERROR: could not save during ProcessConsensusChange:", err) 310 } 311 }