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