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  }