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 }