github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/subscribe.go (about) 1 package consensus 2 3 import ( 4 "github.com/NebulousLabs/Sia/modules" 5 6 "github.com/NebulousLabs/bolt" 7 ) 8 9 // computeConsensusChange computes the consensus change from the change entry 10 // at index 'i' in the change log. If i is out of bounds, an error is returned. 11 func (cs *ConsensusSet) computeConsensusChange(tx *bolt.Tx, ce changeEntry) (modules.ConsensusChange, error) { 12 cc := modules.ConsensusChange{ 13 ID: ce.ID(), 14 } 15 for _, revertedBlockID := range ce.RevertedBlocks { 16 revertedBlock, err := getBlockMap(tx, revertedBlockID) 17 if err != nil { 18 cs.log.Critical("getBlockMap failed in computeConsensusChange:", err) 19 return modules.ConsensusChange{}, err 20 } 21 22 // Because the direction is 'revert', the order of the diffs needs to 23 // be flipped and the direction of the diffs also needs to be flipped. 24 cc.RevertedBlocks = append(cc.RevertedBlocks, revertedBlock.Block) 25 for i := len(revertedBlock.SiacoinOutputDiffs) - 1; i >= 0; i-- { 26 scod := revertedBlock.SiacoinOutputDiffs[i] 27 scod.Direction = !scod.Direction 28 cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod) 29 } 30 for i := len(revertedBlock.FileContractDiffs) - 1; i >= 0; i-- { 31 fcd := revertedBlock.FileContractDiffs[i] 32 fcd.Direction = !fcd.Direction 33 cc.FileContractDiffs = append(cc.FileContractDiffs, fcd) 34 } 35 for i := len(revertedBlock.SiafundOutputDiffs) - 1; i >= 0; i-- { 36 sfod := revertedBlock.SiafundOutputDiffs[i] 37 sfod.Direction = !sfod.Direction 38 cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod) 39 } 40 for i := len(revertedBlock.DelayedSiacoinOutputDiffs) - 1; i >= 0; i-- { 41 dscod := revertedBlock.DelayedSiacoinOutputDiffs[i] 42 dscod.Direction = !dscod.Direction 43 cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod) 44 } 45 for i := len(revertedBlock.SiafundPoolDiffs) - 1; i >= 0; i-- { 46 sfpd := revertedBlock.SiafundPoolDiffs[i] 47 sfpd.Direction = modules.DiffRevert 48 cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd) 49 } 50 } 51 for _, appliedBlockID := range ce.AppliedBlocks { 52 appliedBlock, err := getBlockMap(tx, appliedBlockID) 53 if err != nil { 54 cs.log.Critical("getBlockMap failed in computeConsensusChange:", err) 55 return modules.ConsensusChange{}, err 56 } 57 58 cc.AppliedBlocks = append(cc.AppliedBlocks, appliedBlock.Block) 59 for _, scod := range appliedBlock.SiacoinOutputDiffs { 60 cc.SiacoinOutputDiffs = append(cc.SiacoinOutputDiffs, scod) 61 } 62 for _, fcd := range appliedBlock.FileContractDiffs { 63 cc.FileContractDiffs = append(cc.FileContractDiffs, fcd) 64 } 65 for _, sfod := range appliedBlock.SiafundOutputDiffs { 66 cc.SiafundOutputDiffs = append(cc.SiafundOutputDiffs, sfod) 67 } 68 for _, dscod := range appliedBlock.DelayedSiacoinOutputDiffs { 69 cc.DelayedSiacoinOutputDiffs = append(cc.DelayedSiacoinOutputDiffs, dscod) 70 } 71 for _, sfpd := range appliedBlock.SiafundPoolDiffs { 72 cc.SiafundPoolDiffs = append(cc.SiafundPoolDiffs, sfpd) 73 } 74 } 75 return cc, nil 76 } 77 78 // readlockUpdateSubscribers will inform all subscribers of a new update to the 79 // consensus set. The call must be made with a demoted lock or a readlock. 80 // readlockUpdateSubscribers does not alter the changelog, the changelog must 81 // be updated beforehand. 82 func (cs *ConsensusSet) readlockUpdateSubscribers(ce changeEntry) { 83 // Get the consensus change and send it to all subscribers. 84 var cc modules.ConsensusChange 85 err := cs.db.View(func(tx *bolt.Tx) error { 86 // Compute the consensus change so it can be sent to subscribers. 87 var err error 88 cc, err = cs.computeConsensusChange(tx, ce) 89 return err 90 }) 91 if err != nil { 92 cs.log.Critical("computeConsensusChange failed:", err) 93 return 94 } 95 for _, subscriber := range cs.subscribers { 96 subscriber.ProcessConsensusChange(cc) 97 } 98 } 99 100 // initializeSubscribe will take a subscriber and feed them all of the 101 // consensus changes that have occurred since the change provided. 102 // 103 // As a special case, using an empty id as the start will have all the changes 104 // sent to the modules starting with the genesis block. 105 func (cs *ConsensusSet) initializeSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error { 106 return cs.db.View(func(tx *bolt.Tx) error { 107 // 'exists' and 'entry' are going to be pointed to the first entry that 108 // has not yet been seen by subscriber. 109 var exists bool 110 var entry changeEntry 111 112 if start == modules.ConsensusChangeBeginning { 113 // Special case: for modules.ConsensusChangeBeginning, create an 114 // initial node pointing to the genesis block. The subscriber will 115 // receive the diffs for all blocks in the consensus set, including 116 // the genesis block. 117 entry = cs.genesisEntry() 118 exists = true 119 } else if start == modules.ConsensusChangeRecent { 120 // Special case: for modules.ConsensusChangeRecent, set up the 121 // subscriber to start receiving only new blocks, but the 122 // subscriber does not need to do any catch-up. For this 123 // implementation, a no-op will have this effect. 124 return nil 125 } else { 126 // The subscriber has provided an existing consensus change. 127 // Because the subscriber already has this consensus change, 128 // 'entry' and 'exists' need to be pointed at the next consensus 129 // change. 130 entry, exists = getEntry(tx, start) 131 if !exists { 132 // modules.ErrInvalidConsensusChangeID is a named error that 133 // signals a break in synchronization between the consensus set 134 // persistence and the subscriber persistence. Typically, 135 // receiving this error means that the subscriber needs to 136 // perform a rescan of the consensus set. 137 return modules.ErrInvalidConsensusChangeID 138 } 139 entry, exists = entry.NextEntry(tx) 140 } 141 142 // Send all remaining consensus changes to the subscriber. 143 for exists { 144 cc, err := cs.computeConsensusChange(tx, entry) 145 if err != nil { 146 return err 147 } 148 subscriber.ProcessConsensusChange(cc) 149 entry, exists = entry.NextEntry(tx) 150 } 151 return nil 152 }) 153 } 154 155 // ConsensusSetSubscribe adds a subscriber to the list of subscribers, and 156 // gives them every consensus change that has occurred since the change with 157 // the provided id. 158 // 159 // As a special case, using an empty id as the start will have all the changes 160 // sent to the modules starting with the genesis block. 161 func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error { 162 cs.mu.Lock() 163 cs.subscribers = append(cs.subscribers, subscriber) 164 cs.mu.Demote() 165 defer cs.mu.DemotedUnlock() 166 167 // Get the input module caught up to the currenct consnesus set. 168 err := cs.initializeSubscribe(subscriber, start) 169 if err != nil { 170 return err 171 } 172 // Only add the module as a subscriber if there was no error. 173 return nil 174 } 175 176 // Unsubscribe removes a subscriber from the list of subscribers, allowing for 177 // garbage collection and rescanning. If the subscriber is not found in the 178 // subscriber database, no action is taken. 179 func (cs *ConsensusSet) Unsubscribe(subscriber modules.ConsensusSetSubscriber) { 180 cs.mu.Lock() 181 defer cs.mu.Unlock() 182 183 // Search for the subscriber in the list of subscribers and remove it if 184 // found. 185 for i := range cs.subscribers { 186 if cs.subscribers[i] == subscriber { 187 cs.subscribers = append(cs.subscribers[0:i], cs.subscribers[i+1:]...) 188 break 189 } 190 } 191 }