github.com/decred/dcrlnd@v0.7.6/chainscan/scanner.go (about)

     1  package chainscan
     2  
     3  import (
     4  	"bytes"
     5  	"container/heap"
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/decred/dcrd/chaincfg/chainhash"
    11  	"github.com/decred/dcrd/gcs/v4"
    12  	"github.com/decred/dcrd/wire"
    13  	"github.com/decred/slog"
    14  )
    15  
    16  type MatchField uint8
    17  
    18  const (
    19  	MatchTxIn MatchField = 1 << iota
    20  	MatchTxOut
    21  )
    22  
    23  func (m MatchField) String() string {
    24  	switch m {
    25  	case MatchTxIn:
    26  		return "in"
    27  	case MatchTxOut:
    28  		return "out"
    29  	case MatchTxIn | MatchTxOut:
    30  		return "in|out"
    31  	default:
    32  		return "invalid"
    33  	}
    34  }
    35  
    36  var zeroOutPoint = wire.OutPoint{}
    37  
    38  type ChainSource interface {
    39  	GetBlock(context.Context, *chainhash.Hash) (*wire.MsgBlock, error)
    40  	CurrentTip(context.Context) (*chainhash.Hash, int32, error)
    41  }
    42  
    43  // ErrBlockAfterTip should be returned by chains when the requested block is
    44  // after the currently known tip. Scanners may choose not to fail their run if
    45  // this specific error is returned.
    46  type ErrBlockAfterTip struct {
    47  	Height int32
    48  }
    49  
    50  func (e ErrBlockAfterTip) Error() string {
    51  	return fmt.Sprintf("block %d is after currently known tip", e.Height)
    52  }
    53  
    54  func (e ErrBlockAfterTip) Is(err error) bool {
    55  	_, ok := err.(ErrBlockAfterTip)
    56  	return ok
    57  }
    58  
    59  type Event struct {
    60  	BlockHeight  int32
    61  	BlockHash    chainhash.Hash
    62  	TxIndex      int32
    63  	Tx           *wire.MsgTx
    64  	Index        int32
    65  	Tree         int8
    66  	MatchedField MatchField
    67  }
    68  
    69  func (e Event) String() string {
    70  	if (e.Tx) == nil {
    71  		return "unfilled event"
    72  	}
    73  
    74  	switch e.MatchedField {
    75  	case MatchTxOut:
    76  		return fmt.Sprintf("txOut=%s:%d height=%d",
    77  			e.Tx.CachedTxHash(), e.Index, e.BlockHeight)
    78  	case MatchTxIn:
    79  		return fmt.Sprintf("txIn=%s:%d height=%d",
    80  			e.Tx.CachedTxHash(), e.Index, e.BlockHeight)
    81  	default:
    82  		return "unknown matched field"
    83  	}
    84  }
    85  
    86  type FindFunc func(Target, ...Option) error
    87  type FoundCallback func(Event, FindFunc)
    88  
    89  // Target represents the desired target (outpoint, pkscript, etc) of a search.
    90  //
    91  // NOTE: targets are *not* safe for reuse for multiple searches.
    92  type Target interface {
    93  	// nop is meant to prevent external packages implementing this
    94  	// interface.
    95  	nop()
    96  }
    97  
    98  const (
    99  	flagMatchTxIn = 1 << iota
   100  	flagMatchTxOut
   101  	flagMatchOutpoint
   102  	flagMatchTxHash
   103  )
   104  
   105  type target struct {
   106  	// Fields that specify _what_ to match against
   107  
   108  	flags    int
   109  	out      wire.OutPoint
   110  	version  uint16
   111  	pkscript []byte
   112  
   113  	// Fields that control the _behavior_ during scans.
   114  
   115  	startHeight          int32
   116  	endHeight            int32
   117  	foundCb              FoundCallback
   118  	foundChan            chan<- Event
   119  	completeChan         chan struct{}
   120  	startWatchHeightChan chan int32
   121  	cancelChan           <-chan struct{}
   122  }
   123  
   124  func (c *target) String() string {
   125  	match := "unknown"
   126  	switch c.flags {
   127  	case flagMatchTxIn | flagMatchOutpoint:
   128  		match = "TxIn(outp)"
   129  	case flagMatchTxIn:
   130  		match = "TxIn(script)"
   131  	case flagMatchTxOut | flagMatchOutpoint:
   132  		match = "TxOut(outp)"
   133  	case flagMatchTxOut | flagMatchTxHash:
   134  		match = "TxOut(tx)"
   135  	case flagMatchTxOut:
   136  		match = "TxOut(script)"
   137  	}
   138  
   139  	return fmt.Sprintf("id=%p match=%s pkscript=%x out=%s", c, match,
   140  		c.pkscript, c.out)
   141  }
   142  
   143  func (c *target) canceled() bool {
   144  	select {
   145  	case <-c.cancelChan:
   146  		return true
   147  	default:
   148  		return false
   149  	}
   150  }
   151  
   152  // nop fulfills the Target interface.
   153  func (c *target) nop() {}
   154  
   155  // ConfirmedOutPoint tries to match against a TxOut that spends the provided
   156  // script but only as long as the output itself fulfills the specified outpoint
   157  // (that is, the output is created in the transaction specified by out.Hash and
   158  // at the out.Index position).
   159  func ConfirmedOutPoint(out wire.OutPoint, version uint16, pkscript []byte) Target {
   160  	return &target{
   161  		flags:    flagMatchTxOut | flagMatchOutpoint,
   162  		out:      out,
   163  		version:  version,
   164  		pkscript: pkscript,
   165  	}
   166  }
   167  
   168  func ConfirmedTransaction(txh chainhash.Hash, version uint16, pkscript []byte) Target {
   169  	return &target{
   170  		flags: flagMatchTxOut | flagMatchTxHash,
   171  		out: wire.OutPoint{
   172  			Hash: txh,
   173  		},
   174  		version:  version,
   175  		pkscript: pkscript,
   176  	}
   177  }
   178  
   179  func ConfirmedScript(version uint16, pkscript []byte) Target {
   180  	return &target{
   181  		flags:    flagMatchTxOut,
   182  		version:  version,
   183  		pkscript: pkscript,
   184  	}
   185  }
   186  
   187  // SpentScript tries to match against a TxIn that spends the provided script.
   188  //
   189  // NOTE: only version 0 scripts are currently supported. Also, the match is a
   190  // best effort one, based on the "shape" of the signature script of the txin.
   191  // See ComputePkScript for details on supported script types.
   192  func SpentScript(version uint16, pkscript []byte) Target {
   193  	return &target{
   194  		flags:    flagMatchTxIn,
   195  		version:  version,
   196  		pkscript: pkscript,
   197  	}
   198  }
   199  
   200  // SpentOutPoint tries to match against a TxIn that spends the given outpoint
   201  // and pkscript combination.
   202  //
   203  // NOTE: If the provided pkscript does _not_ actually correspond to the
   204  // outpoint, the search may never trigger a match.
   205  func SpentOutPoint(out wire.OutPoint, version uint16, pkscript []byte) Target {
   206  	return &target{
   207  		flags:    flagMatchTxIn | flagMatchOutpoint,
   208  		out:      out,
   209  		version:  version,
   210  		pkscript: pkscript,
   211  	}
   212  }
   213  
   214  type Option func(*target)
   215  
   216  // WithStartHeight allows a caller to specify a block height after which a scan
   217  // should trigger events when this target is found.
   218  func WithStartHeight(startHeight int32) Option {
   219  	return func(t *target) {
   220  		t.startHeight = startHeight
   221  	}
   222  }
   223  
   224  // WithEndHeight allows a caller to specify a block height after which scans
   225  // should no longer happen.
   226  func WithEndHeight(endHeight int32) Option {
   227  	return func(t *target) {
   228  		t.endHeight = endHeight
   229  	}
   230  }
   231  
   232  // WithFoundCallback allows a caller to specify a callback that is called
   233  // synchronously in relation to a scanner when the related target is found in a
   234  // block.
   235  //
   236  // This callback is called in the same goroutine that performs scans, therefore
   237  // it is *NOT* safe to perform any receives or sends on channels that also
   238  // affect this or other target's searches (such as waiting for a
   239  // StartWatchingHeight, FoundChan or other signals).
   240  //
   241  // It is also generally not advised to perform lengthy operations inside this
   242  // callback since it blocks all other searches from progressing.
   243  //
   244  // This callback is intended to be used in situations where the caller needs to
   245  // add new targets to search for as a result of having found a match within a
   246  // block.
   247  //
   248  // There are two ways to add addicional targets during the execution of the
   249  // foundCallback:
   250  //
   251  //  1. Via additional Find() calls of the scanner (which _are_ safe for direct
   252  //     calling by the foundCallback). This allows callers to start to search for
   253  //     additional targets on any block (either further along the chain or in the
   254  //     past).
   255  //
   256  //  2. Via the second argument to the foundCallback. That function can add
   257  //     targets to search for, starting at the _current_ block, transaction list
   258  //     and transaction index. This is useful (for example) when detecting a
   259  //     specific script was used in an output should trigger a search for other
   260  //     scripts including in the same block. Note that when adding new targets
   261  //     using this method, they MUST include a
   262  //     WithStartingHeight(event.BlockHeight+1) option (even though the search
   263  //     will also be carried in later transactions of the current block being
   264  //     scanned).
   265  //
   266  // The callback function may be called multiple times for a given target.
   267  func WithFoundCallback(cb FoundCallback) Option {
   268  	return func(t *target) {
   269  		t.foundCb = cb
   270  	}
   271  }
   272  
   273  // WithFoundChan allows a caller to specify a channel that receives events when
   274  // a match for a given target is found. This event is called concurrently with
   275  // the rest of the search process.
   276  //
   277  // Callers are responsible for draining this channel once the search completes,
   278  // otherwise data may leak. They should also ensure the channel is _not_ closed
   279  // until the search completes, otherwise the scanner may panic.
   280  //
   281  // The channel may be sent to multiple times.
   282  func WithFoundChan(c chan<- Event) Option {
   283  	return func(t *target) {
   284  		t.foundChan = c
   285  	}
   286  }
   287  
   288  // WithCompleteChan allows a caller to specify a channel that gets closed once
   289  // the endHeight was reached for the target.
   290  func WithCompleteChan(c chan struct{}) Option {
   291  	return func(t *target) {
   292  		t.completeChan = c
   293  	}
   294  }
   295  
   296  // WithCancelChan allows a caller to specify a channel that, once closed,
   297  // removes the provided target from being scanned for.
   298  func WithCancelChan(c <-chan struct{}) Option {
   299  	return func(t *target) {
   300  		t.cancelChan = c
   301  	}
   302  }
   303  
   304  // WithStartWatchHeightChan allows a caller to specify a channel that receives
   305  // the block height after which events will be triggered if the target is
   306  // found.
   307  func WithStartWatchHeightChan(c chan int32) Option {
   308  	return func(t *target) {
   309  		t.startWatchHeightChan = c
   310  	}
   311  }
   312  
   313  type TargetAndOptions struct {
   314  	Target  Target
   315  	Options []Option
   316  }
   317  
   318  const txOutKeyLen = 32
   319  
   320  type txOutKey [txOutKeyLen]byte
   321  
   322  func newTxOutKey(version uint16, pkscript []byte) txOutKey {
   323  	// key := make([]byte, 2+len(pkscript))
   324  	var key txOutKey
   325  	key[0] = byte(version >> 8)
   326  	key[1] = byte(version)
   327  	copy(key[2:], pkscript)
   328  	return key
   329  
   330  }
   331  
   332  // targetSlice is a slice of targets that can be modified in-place by adding
   333  // and removing items via the add and del functions.
   334  //
   335  // This is used to simplify the code of some operations in scanners (vs using
   336  // regular slices).
   337  type targetSlice []*target
   338  
   339  func (ts *targetSlice) del(t *target) {
   340  	s := *ts
   341  	for i := 0; i < len(s); i++ {
   342  		if s[i] == t {
   343  			s[i] = s[len(s)-1]
   344  			s[len(s)-1] = nil
   345  			*ts = s[:len(s)-1]
   346  			return
   347  		}
   348  	}
   349  }
   350  
   351  func (ts *targetSlice) add(t *target) {
   352  	s := *ts
   353  	*ts = append(s, t)
   354  }
   355  
   356  func (ts *targetSlice) empty() bool {
   357  	return len(*ts) == 0
   358  }
   359  
   360  // targetHeap is a sortable slice of targets that fulfills the heap.Interface
   361  // interface by sorting in ascending order of start height.
   362  type targetHeap []*target
   363  
   364  func (th targetHeap) Len() int           { return len(th) }
   365  func (th targetHeap) Less(i, j int) bool { return th[i].startHeight < th[j].startHeight }
   366  func (th targetHeap) Swap(i, j int)      { th[i], th[j] = th[j], th[i] }
   367  
   368  func (th *targetHeap) Push(x interface{}) {
   369  	*th = append(*th, x.(*target))
   370  }
   371  
   372  func (th *targetHeap) Pop() interface{} {
   373  	old := *th
   374  	n := len(old)
   375  	x := old[n-1]
   376  	old[n-1] = nil // Avoid leaking the target.
   377  	*th = old[0 : n-1]
   378  	return x
   379  }
   380  
   381  func (th *targetHeap) push(ts ...*target) {
   382  	for _, t := range ts {
   383  		heap.Push(th, t)
   384  	}
   385  }
   386  
   387  func (th *targetHeap) pop() *target {
   388  	return heap.Pop(th).(*target)
   389  }
   390  
   391  func (th *targetHeap) peak() *target {
   392  	if len(*th) > 0 {
   393  		return (*th)[0]
   394  	}
   395  	return nil
   396  }
   397  
   398  // asTargetHeap returns the slice of targets as a targetHeap. The order of
   399  // elements of the backing array of the given slice may be modified by this
   400  // fuction.
   401  func asTargetHeap(targets []*target) *targetHeap {
   402  	th := targetHeap(targets)
   403  	heap.Init(&th)
   404  	return &th
   405  }
   406  
   407  var _ heap.Interface = (*targetHeap)(nil)
   408  
   409  // targetList is a list of targets which can be queried for matches against
   410  // blocks.
   411  //
   412  // It maintains several caches so that queries for the multiple targets in a
   413  // block can be done only in linear time (on the number of inputs+outputs of
   414  // the transactions).
   415  //
   416  // targetList values aren't safe for concurrent access from multiple
   417  // goroutines.
   418  type targetList struct {
   419  	targets        map[*target]struct{}
   420  	cfEntries      [][]byte
   421  	dirty          bool
   422  	txInKeys       map[wire.OutPoint]*targetSlice
   423  	txInScriptKeys map[txOutKey]*targetSlice
   424  	txOutKeys      map[txOutKey]*targetSlice
   425  
   426  	// blockHeight is the current height being processed during a call for
   427  	// singalFound().
   428  	blockHeight int32
   429  
   430  	// ff stores a reference to the the target list's addDuringFoundCb
   431  	// function. Storing as a field here avoids having to perform an
   432  	// allocation during the critical code path when matches occur in a
   433  	// block.
   434  	ff FindFunc
   435  
   436  	// addedDuringScan tracks new targets added by the ff/addDuringFoundCb
   437  	// during a single scan() call.
   438  	addedDuringScan []*target
   439  }
   440  
   441  func newTargetList(initial []*target) *targetList {
   442  	tl := &targetList{
   443  		targets:        make(map[*target]struct{}, len(initial)),
   444  		txInKeys:       make(map[wire.OutPoint]*targetSlice, len(initial)),
   445  		txInScriptKeys: make(map[txOutKey]*targetSlice, len(initial)),
   446  		txOutKeys:      make(map[txOutKey]*targetSlice, len(initial)),
   447  	}
   448  
   449  	for _, t := range initial {
   450  		tl.add(t)
   451  	}
   452  	tl.rebuildCfilterEntries()
   453  
   454  	tl.ff = FindFunc(tl.addDuringFoundCb)
   455  
   456  	return tl
   457  }
   458  
   459  func (tl *targetList) empty() bool {
   460  	return len(tl.targets) == 0
   461  }
   462  
   463  func (tl *targetList) add(newTargets ...*target) {
   464  	for _, nt := range newTargets {
   465  		if _, ok := tl.targets[nt]; ok {
   466  			continue
   467  		}
   468  
   469  		tl.targets[nt] = struct{}{}
   470  		if nt.flags&flagMatchTxIn == flagMatchTxIn {
   471  			if nt.flags&flagMatchOutpoint == 0 {
   472  				// Match by input script.
   473  				key := newTxOutKey(nt.version, nt.pkscript)
   474  				if _, ok := tl.txInScriptKeys[key]; !ok {
   475  					tl.txInScriptKeys[key] = &targetSlice{}
   476  				}
   477  				tl.txInScriptKeys[key].add(nt)
   478  			} else {
   479  				// Match by outpoint.
   480  				outp := nt.out
   481  				if _, ok := tl.txInKeys[outp]; !ok {
   482  					tl.txInKeys[outp] = &targetSlice{}
   483  				}
   484  				tl.txInKeys[outp].add(nt)
   485  			}
   486  		}
   487  
   488  		if nt.flags&flagMatchTxOut == flagMatchTxOut {
   489  			key := newTxOutKey(nt.version, nt.pkscript)
   490  			if _, ok := tl.txOutKeys[key]; !ok {
   491  				tl.txOutKeys[key] = &targetSlice{}
   492  			}
   493  			tl.txOutKeys[key].add(nt)
   494  		}
   495  	}
   496  
   497  	tl.dirty = true
   498  }
   499  
   500  func (tl *targetList) removeAll() []*target {
   501  	targets := make([]*target, len(tl.targets))
   502  	var i int
   503  	for t := range tl.targets {
   504  		targets[i] = t
   505  	}
   506  	tl.remove(targets...)
   507  	return targets
   508  }
   509  
   510  func (tl *targetList) remove(newTargets ...*target) {
   511  	for _, nt := range newTargets {
   512  		if _, ok := tl.targets[nt]; !ok {
   513  			continue
   514  		}
   515  
   516  		tl.dirty = true
   517  
   518  		if nt.flags&flagMatchTxIn == flagMatchTxIn {
   519  			if nt.out == zeroOutPoint {
   520  				key := newTxOutKey(nt.version, nt.pkscript)
   521  				if _, ok := tl.txInScriptKeys[key]; ok {
   522  					tl.txInScriptKeys[key].del(nt)
   523  					if tl.txInScriptKeys[key].empty() {
   524  						delete(tl.txInScriptKeys, key)
   525  					}
   526  				}
   527  			} else {
   528  				outp := nt.out
   529  				if _, ok := tl.txInKeys[outp]; ok {
   530  					tl.txInKeys[outp].del(nt)
   531  					if tl.txInKeys[outp].empty() {
   532  						delete(tl.txInKeys, outp)
   533  					}
   534  				}
   535  			}
   536  		}
   537  
   538  		if nt.flags&flagMatchTxOut == flagMatchTxOut {
   539  			key := newTxOutKey(nt.version, nt.pkscript)
   540  			if _, ok := tl.txOutKeys[key]; ok {
   541  				tl.txOutKeys[key].del(nt)
   542  				if tl.txOutKeys[key].empty() {
   543  					delete(tl.txOutKeys, key)
   544  				}
   545  			}
   546  		}
   547  
   548  		delete(tl.targets, nt)
   549  	}
   550  }
   551  
   552  // removeStale removes all targets that have been canceled or for which their
   553  // endHeight was reached as specified by the 'height' parameter. This function
   554  // returns only the targets for which the endHeight was reached.
   555  func (tl *targetList) removeStale(height int32) []*target {
   556  	var stale []*target
   557  	for t := range tl.targets {
   558  		del := false
   559  		if height >= t.endHeight {
   560  			// Got to the end of the watching interval for the
   561  			// given target.
   562  			stale = append(stale, t)
   563  			del = true
   564  			log.Tracef("Removing stale target at %d: %s", height, t)
   565  		}
   566  
   567  		select {
   568  		case <-t.cancelChan:
   569  			del = true
   570  			log.Tracef("Removing canceled target at %d: %s", height, t)
   571  		default:
   572  		}
   573  
   574  		if del {
   575  			tl.remove(t)
   576  		}
   577  	}
   578  
   579  	return stale
   580  }
   581  
   582  func (tl *targetList) rebuildCfilterEntries() {
   583  	cf := make([][]byte, 0, len(tl.targets))
   584  	for t := range tl.targets {
   585  		cf = append(cf, t.pkscript)
   586  	}
   587  	tl.cfEntries = cf
   588  	tl.dirty = false
   589  }
   590  
   591  func (tl *targetList) addDuringFoundCb(tgt Target, opts ...Option) error {
   592  	t, ok := tgt.(*target)
   593  	if !ok {
   594  		return errors.New("provided target should be chainscan.*target")
   595  	}
   596  
   597  	for _, opt := range opts {
   598  		opt(t)
   599  	}
   600  
   601  	if t.endHeight > 0 && t.endHeight < tl.blockHeight {
   602  		return errors.New("cannot add targets during foundCb with " +
   603  			"endHeight lower than the current blockHeight")
   604  	}
   605  
   606  	if t.startHeight != tl.blockHeight+1 {
   607  		return errors.New("cannot add targets during foundCb with " +
   608  			"startHeight different than the current blockHeight + 1")
   609  	}
   610  
   611  	log.Tracef("Adding new target %s inside addDuringFoundCb", t)
   612  	tl.add(t)
   613  	tl.addedDuringScan = append(tl.addedDuringScan, t)
   614  	return nil
   615  }
   616  
   617  // signalMatches finds and signals all matches of the current target list in
   618  // the given block.
   619  func (tl *targetList) signalFound(blockHeight int32, blockHash *chainhash.Hash, block *wire.MsgBlock) {
   620  	// Filled after the first match on a transaction is found.
   621  	var txid chainhash.Hash
   622  	var ptxid *chainhash.Hash
   623  
   624  	var event Event
   625  
   626  	tl.blockHeight = blockHeight
   627  
   628  	// Whether there are any inputs that will be matched by script only
   629  	// instead of by outpoint.
   630  	matchByInScript := len(tl.txInScriptKeys) > 0
   631  
   632  	// Flag to test against so we verify a match against a specific output
   633  	// of a confirmed output.
   634  	flagTxOutWithOutp := flagMatchTxOut | flagMatchOutpoint
   635  
   636  	// Fill events for all targets from ts that have matched against the
   637  	// transaction tx at field f and index i.
   638  	fillMatches := func(ts []*target, tx *wire.MsgTx, tree int8, i int32, f MatchField, txi int32) {
   639  		// "large" scripts are those that surpass the the maximum key
   640  		// length size for output matching. In that case, we also need
   641  		// to check the full script on targets.
   642  		//
   643  		// This should be a rare occurrence given the absolute majority
   644  		// of scripts are P2PKH and P2SH.
   645  		//
   646  		// Scripts in inputs don't need this check because
   647  		// ComputePkScript only supports P2PKH and P2SH.
   648  		largeScript := f == MatchTxOut && len(tx.TxOut[i].PkScript) > txOutKeyLen
   649  
   650  		for _, t := range ts {
   651  			// Canceled targets can't be triggered.
   652  			if t.canceled() {
   653  				continue
   654  			}
   655  
   656  			// Large scripts that don't match aren't signalled.
   657  			if largeScript && !bytes.Equal(t.pkscript, tx.TxOut[i].PkScript) {
   658  				continue
   659  			}
   660  
   661  			if t.flags&flagTxOutWithOutp == flagTxOutWithOutp {
   662  				// When matching against TxOut's, if the
   663  				// outpoint in the target is specified we only
   664  				// match against that specific output. So we
   665  				// need to calculate the txid and verify the
   666  				// target's outpoint with the txid+index.
   667  				//
   668  				// We check the index and tree first since
   669  				// there's no point in calculating the txid if
   670  				// the other fields don't match.
   671  				if i != int32(t.out.Index) || tree != t.out.Tree {
   672  					continue
   673  				}
   674  
   675  				// Calculate tx hash if needed.
   676  				if ptxid == nil {
   677  					txid = tx.TxHash()
   678  					ptxid = &txid
   679  				}
   680  
   681  				if txid != t.out.Hash {
   682  					continue
   683  				}
   684  			}
   685  
   686  			// Match against only the tx hash.
   687  			if t.flags&flagMatchTxHash == flagMatchTxHash {
   688  				// Calculate tx hash if needed.
   689  				if ptxid == nil {
   690  					txid = tx.TxHash()
   691  					ptxid = &txid
   692  				}
   693  
   694  				if txid != t.out.Hash {
   695  					continue
   696  				}
   697  			}
   698  
   699  			// Found a match!
   700  			event = Event{
   701  				BlockHeight:  blockHeight,
   702  				BlockHash:    *blockHash,
   703  				TxIndex:      txi,
   704  				Tx:           tx,
   705  				Index:        i,
   706  				Tree:         tree,
   707  				MatchedField: f,
   708  			}
   709  
   710  			if log.Level() <= slog.LevelDebug {
   711  				log.Debugf("Matched %s for target %s", event, t)
   712  			}
   713  
   714  			if t.foundCb != nil {
   715  				// foundCb() is called synchronously so that it
   716  				// can modify scanners before the scan
   717  				// continues.
   718  				t.foundCb(event, tl.ff)
   719  				matchByInScript = len(tl.txInScriptKeys) > 0
   720  			}
   721  			if t.foundChan != nil {
   722  				// foundChan is signalled asynchronously so
   723  				// scans aren't blocked.
   724  				go func(c chan<- Event, e Event) {
   725  					c <- e
   726  				}(t.foundChan, event)
   727  			}
   728  		}
   729  	}
   730  
   731  	// Process both the regular and stake transaction trees.
   732  	trees := []int8{wire.TxTreeRegular, wire.TxTreeStake}
   733  	var txs []*wire.MsgTx
   734  
   735  	for _, tree := range trees {
   736  		switch tree {
   737  		case wire.TxTreeStake:
   738  			txs = block.STransactions
   739  		default:
   740  			txs = block.Transactions
   741  		}
   742  
   743  		for txi, tx := range txs {
   744  			ptxid = nil
   745  			for i, in := range tx.TxIn {
   746  				if ts, ok := tl.txInKeys[in.PreviousOutPoint]; ok {
   747  					fillMatches(*ts, tx, tree, int32(i), MatchTxIn, int32(txi))
   748  				}
   749  
   750  				// Only continue to process the input if we
   751  				// need to match by input script.
   752  				if !matchByInScript {
   753  					continue
   754  				}
   755  
   756  				script, err := ComputePkScript(0, in.SignatureScript)
   757  				if err != nil {
   758  					// If this is an unrecognized script
   759  					// type it can't possibly be a match.
   760  					continue
   761  				}
   762  
   763  				// Guessing it's a version 0 script. How to
   764  				// support other types?
   765  				key := newTxOutKey(0, script.Script())
   766  				if ts, ok := tl.txInScriptKeys[key]; ok {
   767  					fillMatches(*ts, tx, tree, int32(i), MatchTxIn, int32(txi))
   768  				}
   769  			}
   770  
   771  			for i, out := range tx.TxOut {
   772  				key := newTxOutKey(out.Version, out.PkScript)
   773  				if ts, ok := tl.txOutKeys[key]; ok {
   774  					fillMatches(*ts, tx, tree, int32(i), MatchTxOut, int32(txi))
   775  				}
   776  			}
   777  		}
   778  	}
   779  }
   780  
   781  // signalComplete closes the completeChan of all targets in the specified
   782  // slice.
   783  func signalComplete(targets []*target) {
   784  	for _, t := range targets {
   785  		if t.completeChan != nil {
   786  			close(t.completeChan)
   787  		}
   788  	}
   789  }
   790  
   791  // signalStartWatchHeight sends the given height as the start watching height
   792  // for all applicable targets in the slice.
   793  func signalStartWatchHeight(targets []*target, height int32) {
   794  	for _, t := range targets {
   795  		if t.startWatchHeightChan != nil {
   796  			go func(c chan int32) {
   797  				c <- height
   798  			}(t.startWatchHeightChan)
   799  		}
   800  	}
   801  }
   802  
   803  // blockCFilter is an auxillary structure used to hold all data required to
   804  // query a v2 cfilter of a given block.
   805  type blockCFilter struct {
   806  	hash       *chainhash.Hash
   807  	height     int32
   808  	cfilterKey [16]byte
   809  	cfilter    *gcs.FilterV2
   810  }
   811  
   812  func (bcf blockCFilter) matches(entries [][]byte) bool {
   813  	return bcf.cfilter.MatchAny(bcf.cfilterKey, entries)
   814  }
   815  
   816  // scan performs a cfilter and then (if needed) a full block scan in the given
   817  // blockcf for the specified target list.
   818  //
   819  // getBlock must be able to fetch the specified full block data.
   820  //
   821  // Note that the `targets` target list may have been modified by this call,
   822  // therefore callers should check whether the target list is dirty and rebuild
   823  // cfilter entries as appropriate.
   824  func scan(ctx context.Context, blockcf *blockCFilter, targets *targetList, getBlock func(context.Context, *chainhash.Hash) (*wire.MsgBlock, error)) error {
   825  	targets.addedDuringScan = nil
   826  	if !blockcf.matches(targets.cfEntries) {
   827  		return nil
   828  	}
   829  
   830  	// Find and process matches in the actual block, given the cfilter test
   831  	// passed.
   832  	block, err := getBlock(ctx, blockcf.hash)
   833  	if err != nil {
   834  		return err
   835  	}
   836  
   837  	// Alert clients of matches found.
   838  	targets.signalFound(blockcf.height, blockcf.hash, block)
   839  
   840  	return nil
   841  }