github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/subscribe.go (about) 1 package consensus 2 3 import ( 4 "errors" 5 "time" 6 7 "github.com/NebulousLabs/Sia/build" 8 "github.com/NebulousLabs/Sia/modules" 9 10 siasync "github.com/NebulousLabs/Sia/sync" 11 "github.com/coreos/bbolt" 12 ) 13 14 // computeConsensusChange computes the consensus change from the change entry 15 // at index 'i' in the change log. If i is out of bounds, an error is returned. 16 func (cs *ConsensusSet) computeConsensusChange(tx *bolt.Tx, ce changeEntry) (modules.ConsensusChange, error) { 17 cc := modules.ConsensusChange{ 18 ID: ce.ID(), 19 } 20 for _, revertedBlockID := range ce.RevertedBlocks { 21 revertedBlock, err := getBlockMap(tx, revertedBlockID) 22 if err != nil { 23 cs.log.Critical("getBlockMap failed in computeConsensusChange:", err) 24 return modules.ConsensusChange{}, err 25 } 26 27 // Because the direction is 'revert', the order of the diffs needs to 28 // be flipped and the direction of the diffs also needs to be flipped. 29 cc.RevertedBlocks = append(cc.RevertedBlocks, revertedBlock.Block) 30 for i := len(revertedBlock.SiacoinOutputDiffs) - 1; i >= 0; i-- { 31 scod := revertedBlock.SiacoinOutputDiffs[i] 32 scod.Direction = !scod.Direction 33 cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod) 34 } 35 for i := len(revertedBlock.FileContractDiffs) - 1; i >= 0; i-- { 36 fcd := revertedBlock.FileContractDiffs[i] 37 fcd.Direction = !fcd.Direction 38 cc.FileContractDiffs = append(cc.FileContractDiffs, fcd) 39 } 40 for i := len(revertedBlock.SiafundOutputDiffs) - 1; i >= 0; i-- { 41 sfod := revertedBlock.SiafundOutputDiffs[i] 42 sfod.Direction = !sfod.Direction 43 cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod) 44 } 45 for i := len(revertedBlock.DelayedSiacoinOutputDiffs) - 1; i >= 0; i-- { 46 dscod := revertedBlock.DelayedSiacoinOutputDiffs[i] 47 dscod.Direction = !dscod.Direction 48 cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod) 49 } 50 for i := len(revertedBlock.SiafundPoolDiffs) - 1; i >= 0; i-- { 51 sfpd := revertedBlock.SiafundPoolDiffs[i] 52 sfpd.Direction = modules.DiffRevert 53 cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd) 54 } 55 } 56 for _, appliedBlockID := range ce.AppliedBlocks { 57 appliedBlock, err := getBlockMap(tx, appliedBlockID) 58 if err != nil { 59 cs.log.Critical("getBlockMap failed in computeConsensusChange:", err) 60 return modules.ConsensusChange{}, err 61 } 62 63 cc.AppliedBlocks = append(cc.AppliedBlocks, appliedBlock.Block) 64 for _, scod := range appliedBlock.SiacoinOutputDiffs { 65 cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod) 66 } 67 for _, fcd := range appliedBlock.FileContractDiffs { 68 cc.FileContractDiffs = append(cc.FileContractDiffs, fcd) 69 } 70 for _, sfod := range appliedBlock.SiafundOutputDiffs { 71 cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod) 72 } 73 for _, dscod := range appliedBlock.DelayedSiacoinOutputDiffs { 74 cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod) 75 } 76 for _, sfpd := range appliedBlock.SiafundPoolDiffs { 77 cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd) 78 } 79 } 80 81 // Grab the child target and the minimum valid child timestamp. 82 recentBlock := ce.AppliedBlocks[len(ce.AppliedBlocks)-1] 83 pb, err := getBlockMap(tx, recentBlock) 84 if err != nil { 85 cs.log.Critical("could not find process block for known block") 86 } 87 cc.ChildTarget = pb.ChildTarget 88 cc.MinimumValidChildTimestamp = cs.blockRuleHelper.minimumValidChildTimestamp(tx.Bucket(BlockMap), pb) 89 90 currentBlock := currentBlockID(tx) 91 if cs.synced && recentBlock == currentBlock { 92 cc.Synced = true 93 } 94 95 // Add the unexported tryTransactionSet function. 96 cc.TryTransactionSet = cs.tryTransactionSet 97 98 return cc, nil 99 } 100 101 // updateSubscribers will inform all subscribers of a new update to the 102 // consensus set. updateSubscribers does not alter the changelog, the changelog 103 // must be updated beforehand. 104 func (cs *ConsensusSet) updateSubscribers(ce changeEntry) { 105 if len(cs.subscribers) == 0 { 106 return 107 } 108 // Get the consensus change and send it to all subscribers. 109 var cc modules.ConsensusChange 110 err := cs.db.View(func(tx *bolt.Tx) error { 111 // Compute the consensus change so it can be sent to subscribers. 112 var err error 113 cc, err = cs.computeConsensusChange(tx, ce) 114 return err 115 }) 116 if err != nil { 117 cs.log.Critical("computeConsensusChange failed:", err) 118 return 119 } 120 for _, subscriber := range cs.subscribers { 121 subscriber.ProcessConsensusChange(cc) 122 } 123 } 124 125 // managedInitializeSubscribe will take a subscriber and feed them all of the 126 // consensus changes that have occurred since the change provided. 127 // 128 // As a special case, using an empty id as the start will have all the changes 129 // sent to the modules starting with the genesis block. 130 func (cs *ConsensusSet) managedInitializeSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID, 131 cancel <-chan struct{}) (modules.ConsensusChangeID, error) { 132 133 if start == modules.ConsensusChangeRecent { 134 cs.mu.Lock() 135 defer cs.mu.Unlock() 136 return cs.recentConsensusChangeID() 137 } 138 139 // 'exists' and 'entry' are going to be pointed to the first entry that 140 // has not yet been seen by subscriber. 141 var exists bool 142 var entry changeEntry 143 cs.mu.RLock() 144 err := cs.db.View(func(tx *bolt.Tx) error { 145 if start == modules.ConsensusChangeBeginning { 146 // Special case: for modules.ConsensusChangeBeginning, create an 147 // initial node pointing to the genesis block. The subscriber will 148 // receive the diffs for all blocks in the consensus set, including 149 // the genesis block. 150 entry = cs.genesisEntry() 151 exists = true 152 } else { 153 // The subscriber has provided an existing consensus change. 154 // Because the subscriber already has this consensus change, 155 // 'entry' and 'exists' need to be pointed at the next consensus 156 // change. 157 entry, exists = getEntry(tx, start) 158 if !exists { 159 // modules.ErrInvalidConsensusChangeID is a named error that 160 // signals a break in synchronization between the consensus set 161 // persistence and the subscriber persistence. Typically, 162 // receiving this error means that the subscriber needs to 163 // perform a rescan of the consensus set. 164 return modules.ErrInvalidConsensusChangeID 165 } 166 entry, exists = entry.NextEntry(tx) 167 } 168 return nil 169 }) 170 cs.mu.RUnlock() 171 if err != nil { 172 return modules.ConsensusChangeID{}, err 173 } 174 175 // Nothing to do if the changeEntry doesn't exist. 176 if !exists { 177 return start, nil 178 } 179 180 // Send all remaining consensus changes to the subscriber. 181 latestChangeID := entry.ID() 182 for exists { 183 // Send changes in batches of 100 so that we don't hold the 184 // lock for too long. 185 cs.mu.RLock() 186 err = cs.db.View(func(tx *bolt.Tx) error { 187 for i := 0; i < 100 && exists; i++ { 188 latestChangeID = entry.ID() 189 select { 190 case <-cancel: 191 return siasync.ErrStopped 192 default: 193 } 194 cc, err := cs.computeConsensusChange(tx, entry) 195 if err != nil { 196 return err 197 } 198 subscriber.ProcessConsensusChange(cc) 199 entry, exists = entry.NextEntry(tx) 200 } 201 return nil 202 }) 203 cs.mu.RUnlock() 204 if err != nil { 205 return modules.ConsensusChangeID{}, err 206 } 207 } 208 return latestChangeID, nil 209 } 210 211 // recentConsensusChangeID gets the ConsensusChangeID of the most recent 212 // change. 213 func (cs *ConsensusSet) recentConsensusChangeID() (cid modules.ConsensusChangeID, err error) { 214 err = cs.db.View(func(tx *bolt.Tx) error { 215 cl := tx.Bucket(ChangeLog) 216 d := cl.Get(ChangeLogTailID) 217 if d == nil { 218 return errors.New("failed to retrieve recentConsensusChangeID") 219 } 220 copy(cid[:], d[:]) 221 return nil 222 }) 223 return 224 } 225 226 // ConsensusSetSubscribe adds a subscriber to the list of subscribers, and 227 // gives them every consensus change that has occurred since the change with 228 // the provided id. 229 // 230 // As a special case, using an empty id as the start will have all the changes 231 // sent to the modules starting with the genesis block. 232 func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID, 233 cancel <-chan struct{}) error { 234 235 err := cs.tg.Add() 236 if err != nil { 237 return err 238 } 239 defer cs.tg.Done() 240 241 // Call managedInitializeSubscribe until the new module is up-to-date. 242 for { 243 start, err = cs.managedInitializeSubscribe(subscriber, start, cancel) 244 if err != nil { 245 return err 246 } 247 if cs.staticDeps.Disrupt("SleepAfterInitializeSubscribe") { 248 time.Sleep(10 * time.Second) 249 } 250 // Check if the start equals the most recent change id. If it does we 251 // are done. If it doesn't, we need to call managedInitializeSubscribe 252 // again. 253 cs.mu.Lock() 254 recentID, err := cs.recentConsensusChangeID() 255 if err != nil { 256 cs.mu.Unlock() 257 return err 258 } 259 if start == recentID { 260 // break out of the loop while still holding to lock to avoid 261 // updating subscribers before the new module is appended to the 262 // list of subscribers. 263 defer cs.mu.Unlock() 264 break 265 } 266 cs.mu.Unlock() 267 268 // Check for shutdown. 269 select { 270 case <-cs.tg.StopChan(): 271 return siasync.ErrStopped 272 default: 273 } 274 } 275 276 // Add the module to the list of subscribers. 277 // Sanity check - subscriber should not be already subscribed. 278 for _, s := range cs.subscribers { 279 if s == subscriber { 280 build.Critical("refusing to double-subscribe subscriber") 281 } 282 } 283 cs.subscribers = append(cs.subscribers, subscriber) 284 return nil 285 } 286 287 // Unsubscribe removes a subscriber from the list of subscribers, allowing for 288 // garbage collection and rescanning. If the subscriber is not found in the 289 // subscriber database, no action is taken. 290 func (cs *ConsensusSet) Unsubscribe(subscriber modules.ConsensusSetSubscriber) { 291 if cs.tg.Add() != nil { 292 return 293 } 294 defer cs.tg.Done() 295 cs.mu.Lock() 296 defer cs.mu.Unlock() 297 298 // Search for the subscriber in the list of subscribers and remove it if 299 // found. 300 for i := range cs.subscribers { 301 if cs.subscribers[i] == subscriber { 302 // nil the subscriber entry (otherwise it will not be GC'd if it's 303 // at the end of the subscribers slice). 304 cs.subscribers[i] = nil 305 // Delete the entry from the slice. 306 cs.subscribers = append(cs.subscribers[0:i], cs.subscribers[i+1:]...) 307 break 308 } 309 } 310 }