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