github.com/decred/dcrlnd@v0.7.6/chainscan/tip.go (about) 1 package chainscan 2 3 import ( 4 "context" 5 "errors" 6 "sync" 7 "sync/atomic" 8 9 "github.com/decred/dcrd/chaincfg/chainhash" 10 "github.com/decred/dcrd/gcs/v4" 11 "github.com/decred/dcrd/wire" 12 ) 13 14 type ChainEvent interface { 15 BlockHash() *chainhash.Hash 16 BlockHeight() int32 17 PrevBlockHash() *chainhash.Hash 18 BlockHeader() *wire.BlockHeader 19 20 nop() 21 } 22 23 type BlockConnectedEvent struct { 24 Hash chainhash.Hash 25 Height int32 26 PrevHash chainhash.Hash 27 CFKey [16]byte 28 Filter *gcs.FilterV2 29 Header *wire.BlockHeader 30 } 31 32 func (e BlockConnectedEvent) BlockHash() *chainhash.Hash { return &e.Hash } 33 func (e BlockConnectedEvent) BlockHeight() int32 { return e.Height } 34 func (e BlockConnectedEvent) PrevBlockHash() *chainhash.Hash { return &e.PrevHash } 35 func (e BlockConnectedEvent) BlockHeader() *wire.BlockHeader { return e.Header } 36 func (e BlockConnectedEvent) nop() {} 37 func (e BlockConnectedEvent) blockCF() *blockCFilter { 38 return &blockCFilter{ 39 hash: &e.Hash, 40 height: e.Height, 41 cfilterKey: e.CFKey, 42 cfilter: e.Filter, 43 } 44 } 45 46 type BlockDisconnectedEvent struct { 47 Hash chainhash.Hash 48 Height int32 49 PrevHash chainhash.Hash 50 Header *wire.BlockHeader 51 } 52 53 func (e BlockDisconnectedEvent) BlockHash() *chainhash.Hash { return &e.Hash } 54 func (e BlockDisconnectedEvent) BlockHeight() int32 { return e.Height } 55 func (e BlockDisconnectedEvent) BlockHeader() *wire.BlockHeader { return e.Header } 56 func (e BlockDisconnectedEvent) PrevBlockHash() *chainhash.Hash { return &e.PrevHash } 57 func (e BlockDisconnectedEvent) nop() {} 58 59 // TipChainSource defines the required backend functions for the TipWatcher 60 // scanner to perform its duties. 61 type TipChainSource interface { 62 ChainSource 63 64 // ChainEvents MUST return a channel that is sent ChainEvent's until 65 // the passed context is canceled. 66 ChainEvents(context.Context) <-chan ChainEvent 67 } 68 69 type eventReader struct { 70 ctx context.Context 71 c chan ChainEvent 72 } 73 74 type forcedRescan struct { 75 e BlockConnectedEvent 76 done chan error 77 } 78 79 type TipWatcher struct { 80 ctx context.Context 81 running int32 // CAS 1=already running 82 83 newTargetsChan chan []*target 84 85 chain TipChainSource 86 87 mtx sync.Mutex 88 eventReaders []*eventReader 89 90 forcedRescanChan chan forcedRescan 91 92 // The following fields are only used during testing. 93 94 // If specified, after processing a new tip it will be signalled with 95 // the new tip. 96 tipProcessed chan *blockCFilter 97 98 // If specified, Find() blocks until the target has been received by 99 // the Run() goroutine. 100 syncFind bool 101 } 102 103 func NewTipWatcher(chain TipChainSource) *TipWatcher { 104 return &TipWatcher{ 105 chain: chain, 106 newTargetsChan: make(chan []*target), 107 forcedRescanChan: make(chan forcedRescan), 108 } 109 } 110 111 // ChainEvents follows the same semantics as the TipChainSource ChainEvents but 112 // ensures all channels are only signalled _after_ the tip watcher has 113 // processed them. 114 func (tw *TipWatcher) ChainEvents(ctx context.Context) <-chan ChainEvent { 115 r := &eventReader{ 116 ctx: ctx, 117 c: make(chan ChainEvent), 118 } 119 tw.mtx.Lock() 120 tw.eventReaders = append(tw.eventReaders, r) 121 tw.mtx.Unlock() 122 return r.c 123 } 124 125 func (tw *TipWatcher) signalEventReaders(e ChainEvent) { 126 tw.mtx.Lock() 127 readers := tw.eventReaders 128 tw.mtx.Unlock() 129 130 for _, er := range readers { 131 select { 132 case <-er.ctx.Done(): 133 case er.c <- e: 134 } 135 } 136 } 137 138 // targetsForTipWatching splits a list of targets into three sublists assuming 139 // the current tip height of 'height': 140 // 141 // - List of new targets that need to be watched. 142 // - List of waiting targets which have not yet reached their watching height. 143 // - List of stale targets for which their end height was reached. 144 func targetsForTipWatching(height int32, targets []*target) ([]*target, []*target, []*target) { 145 var newTargets, staleTargets, waitingTargets []*target 146 147 for _, t := range targets { 148 switch { 149 // A canceled target can't be watched for. 150 case t.canceled(): 151 log.Tracef("Canceled target for tip watching: %s", t) 152 153 // endHeight for watching this target already passed, so this 154 // is actually a stale target. 155 case t.endHeight != 0 && t.endHeight <= height: 156 staleTargets = append(staleTargets, t) 157 log.Tracef("Stale target for tip watching: %s", t) 158 159 // New target to be watched. 160 case t.startHeight <= height: 161 newTargets = append(newTargets, t) 162 log.Tracef("New target for tip watching: %s", t) 163 164 // Haven't reached the height to start watching this target 165 // yet. 166 default: 167 waitingTargets = append(waitingTargets, t) 168 log.Tracef("Waiting target tip for watching: %s", t) 169 } 170 } 171 172 return newTargets, staleTargets, waitingTargets 173 } 174 175 func (tw *TipWatcher) Run(ctx context.Context) error { 176 if !atomic.CompareAndSwapInt32(&tw.running, 0, 1) { 177 return errors.New("already running") 178 } 179 180 tw.ctx = ctx 181 defer func() { 182 tw.ctx = nil 183 atomic.StoreInt32(&tw.running, 0) 184 }() 185 186 _, tipHeight, err := tw.chain.CurrentTip(ctx) 187 if err != nil { 188 return err 189 } 190 191 // Setup the chan that receives chain events. 192 ceCtx, cancelCe := context.WithCancel(context.Background()) 193 defer cancelCe() 194 chainEvents := tw.chain.ChainEvents(ceCtx) 195 196 var waitingTargets []*target 197 targets := newTargetList(nil) 198 for { 199 select { 200 case <-ctx.Done(): 201 return ctx.Err() 202 203 case newTargets := <-tw.newTargetsChan: 204 waitingTargets = append(waitingTargets, newTargets...) 205 206 case fr := <-tw.forcedRescanChan: 207 log.Debugf("Forcing rescan of (height=%d hash=%s)", 208 fr.e.Height, fr.e.Hash) 209 210 // We ignore errors here because we may have been 211 // provided a wrong block hash (for example). A 212 // canceled tw.ctx will be looked for in the next 213 // iteration of the loop. 214 fr.done <- scan(tw.ctx, fr.e.blockCF(), targets, tw.chain.GetBlock) 215 216 case ce := <-chainEvents: 217 // Ignore block disconnections. 218 e, ok := ce.(BlockConnectedEvent) 219 if !ok { 220 log.Tracef("TipWatcher ignoring disconnect (height=%d hash=%s)", 221 ce.BlockHeight(), ce.BlockHash()) 222 tw.signalEventReaders(ce) 223 continue 224 } 225 226 log.Debugf("TipWatcher next tip received (height=%d hash=%s)", 227 e.Height, e.Hash) 228 err := scan(tw.ctx, e.blockCF(), targets, tw.chain.GetBlock) 229 if err != nil { 230 return err 231 } 232 tipHeight = e.Height 233 234 stale := targets.removeStale(tipHeight) 235 signalComplete(stale) 236 237 tw.signalEventReaders(ce) 238 if tw.tipProcessed != nil { 239 tw.tipProcessed <- e.blockCF() 240 } 241 } 242 243 // Update the list of watched targets with any new ones or ones 244 // that might have been waiting for the startHeight to be 245 // reached. 246 brandNew, waiting, stale := targetsForTipWatching(tipHeight, waitingTargets) 247 signalComplete(stale) 248 waitingTargets = waiting 249 targets.add(brandNew...) 250 signalStartWatchHeight(brandNew, tipHeight) 251 252 if targets.dirty { 253 targets.rebuildCfilterEntries() 254 } 255 } 256 } 257 258 func (tw *TipWatcher) ForceRescan(ctx context.Context, e *BlockConnectedEvent) error { 259 fr := forcedRescan{ 260 e: *e, 261 done: make(chan error), 262 } 263 select { 264 case tw.forcedRescanChan <- fr: 265 case <-ctx.Done(): 266 return ctx.Err() 267 } 268 269 return <-fr.done 270 } 271 272 func (tw *TipWatcher) Find(tgt Target, opts ...Option) error { 273 t, ok := tgt.(*target) 274 if !ok { 275 return errors.New("provided target should be chainscan.*target") 276 } 277 278 for _, opt := range opts { 279 opt(t) 280 } 281 282 switch { 283 case t.endHeight < 0: 284 return errors.New("cannot tip watch with endHeight < 0") 285 case t.endHeight == 0: 286 // Maximum endHeight so the target never goes stale. 287 t.endHeight = 1<<31 - 1 288 } 289 290 // syncFind should only be specified during tests since it risks 291 // deadlocking the tipWatcher. 292 if tw.syncFind { 293 tw.newTargetsChan <- []*target{t} 294 return nil 295 } 296 297 go func() { 298 tw.newTargetsChan <- []*target{t} 299 }() 300 301 return nil 302 }