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

     1  package chainscan
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/decred/dcrd/wire"
     9  )
    10  
    11  type histTestCtx struct {
    12  	chain  *mockChain
    13  	hist   *Historical
    14  	cancel func()
    15  	t      testingIntf
    16  }
    17  
    18  func newHistTestCtx(t testingIntf) *histTestCtx {
    19  	ctx, cancel := context.WithCancel(context.Background())
    20  	chain := newMockChain()
    21  	hist := NewHistorical(chain)
    22  	chain.extend(chain.newFromTip()) // Genesis block
    23  
    24  	go func() {
    25  		hist.Run(ctx)
    26  	}()
    27  
    28  	return &histTestCtx{
    29  		chain:  chain,
    30  		hist:   hist,
    31  		cancel: cancel,
    32  		t:      t,
    33  	}
    34  }
    35  
    36  func (h *histTestCtx) cleanup() {
    37  	h.cancel()
    38  }
    39  
    40  func (h *histTestCtx) genBlocks(n int, allCfiltersMatch bool) {
    41  	h.t.Helper()
    42  	var manglers []blockMangler
    43  
    44  	if allCfiltersMatch {
    45  		manglers = append(manglers, cfilterData(testPkScript))
    46  	}
    47  
    48  	h.chain.genBlocks(n, manglers...)
    49  }
    50  
    51  // TestHistorical tests the basic behavior of the historical scanner against
    52  // the scannerTestCases, which must be fulfilled by both the historical and tip
    53  // watcher scanners.
    54  func TestHistorical(t *testing.T) {
    55  
    56  	runTC := func(c scannerTestCase, t *testing.T) {
    57  		var foundCbEvent, foundChanEvent Event
    58  		completeChan := make(chan struct{})
    59  		foundChan := make(chan Event)
    60  		tc := newHistTestCtx(t)
    61  		defer tc.cleanup()
    62  
    63  		// The generated test chain is:
    64  		// - 5 blocks that miss the cfilter match
    65  		// - 5 blocks with a cfilter match
    66  		// - block with test case manglers
    67  		// - 5 blocks with a cfilter match
    68  		tc.genBlocks(5, false)
    69  		tc.genBlocks(5, true)
    70  		b := tc.chain.newFromTip(c.manglers...)
    71  		tc.chain.extend(b)
    72  		tc.genBlocks(5, true)
    73  
    74  		assertNoError(t, tc.hist.Find(
    75  			c.target(b),
    76  			WithFoundCallback(func(e Event, _ FindFunc) { foundCbEvent = e }),
    77  			WithFoundChan(foundChan),
    78  			WithCompleteChan(completeChan),
    79  		))
    80  
    81  		// Wait until the search is complete.
    82  		assertCompleted(tc.t, completeChan)
    83  
    84  		if !c.wantFound {
    85  			// Testing when we don't expect a match.
    86  
    87  			assertFoundChanEmpty(tc.t, foundChan)
    88  			if foundCbEvent != emptyEvent {
    89  				t.Fatalf("unexpected foundCallback triggered with %s", &foundCbEvent)
    90  			}
    91  
    92  			// Nothing else to test since we didn't expect a match.
    93  			return
    94  		}
    95  
    96  		// Testing when we expect a match.
    97  
    98  		select {
    99  		case foundChanEvent = <-foundChan:
   100  		case <-time.After(5 * time.Second):
   101  			t.Fatal("found chan not triggered in time")
   102  		}
   103  
   104  		if foundCbEvent == emptyEvent {
   105  			t.Fatal("foundCallback not triggered")
   106  		}
   107  
   108  		if foundChanEvent != foundCbEvent {
   109  			t.Fatal("cb and chan showed different events")
   110  		}
   111  
   112  		e := foundChanEvent
   113  		if e.MatchedField != c.wantMF {
   114  			t.Fatalf("unexpected matched field. want=%s got=%s",
   115  				c.wantMF, e.MatchedField)
   116  		}
   117  
   118  		if e.BlockHeight != int32(b.block.Header.Height) {
   119  			t.Fatalf("unexpected matched block height. want=%d got=%d",
   120  				b.block.Header.Height, e.BlockHeight)
   121  		}
   122  
   123  		if e.BlockHash != b.block.Header.BlockHash() {
   124  			t.Fatalf("unexpected matched block hash. want=%s got=%s",
   125  				b.block.Header.BlockHash(), e.BlockHash)
   126  		}
   127  
   128  		// All tests always match against the first transaction in the
   129  		// block in either the stake or regular tx tree.
   130  		var tx *wire.MsgTx
   131  		var tree int8
   132  		if len(b.block.Transactions) > 0 {
   133  			tx = b.block.Transactions[0]
   134  			tree = wire.TxTreeRegular
   135  		} else {
   136  			tx = b.block.STransactions[0]
   137  			tree = wire.TxTreeStake
   138  		}
   139  		if e.Tx.TxHash() != tx.TxHash() {
   140  			t.Fatalf("unexpected tx match. want=%s got=%s",
   141  				b.block.Transactions[0].TxHash(), e.Tx.TxHash())
   142  		}
   143  
   144  		// All tests always match against the second input or output.
   145  		if e.Index != 1 {
   146  			t.Fatalf("unexpected index match. want=%d got=%d",
   147  				1, e.Index)
   148  		}
   149  
   150  		if e.Tree != tree {
   151  			t.Fatalf("unexpected tree match. want=%d got=%d",
   152  				tree, e.Tree)
   153  		}
   154  	}
   155  
   156  	for _, c := range scannerTestCases {
   157  		c := c
   158  		ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) })
   159  		if !ok {
   160  			break
   161  		}
   162  	}
   163  }
   164  
   165  // TestHistoricalCancellation tests that cancelling the search for a target
   166  // before it's found makes it actually not get found.
   167  func TestHistoricalCancellation(t *testing.T) {
   168  	completeChan := make(chan struct{})
   169  	cancelChan := make(chan struct{})
   170  	foundChan := make(chan Event)
   171  	tc := newHistTestCtx(t)
   172  	defer tc.cleanup()
   173  
   174  	// The generated test chain is:
   175  	// - 5 blocks that miss the cfilter match
   176  	// - 5 blocks with a cfilter match
   177  	// - block with test case manglers
   178  	// - 5 blocks with a cfilter match
   179  	tc.genBlocks(5, false)
   180  	tc.genBlocks(5, true)
   181  	b := tc.chain.newFromTip(
   182  		confirmScript(testPkScript),
   183  		cfilterData(testPkScript),
   184  	)
   185  	tc.chain.extend(b)
   186  	tc.genBlocks(5, true)
   187  
   188  	// Instrument the mock chain so we can stop the search mid-way through.
   189  	tc.chain.sendNextCfilterChan = make(chan struct{})
   190  
   191  	// Start the search.
   192  	tc.hist.Find(
   193  		ConfirmedScript(0, testPkScript),
   194  		WithFoundChan(foundChan),
   195  		WithCompleteChan(completeChan),
   196  		WithCancelChan(cancelChan),
   197  	)
   198  
   199  	// Allow the first 10 blocks to be scanned.
   200  	for i := 0; i < 10; i++ {
   201  		tc.chain.sendNextCfilterChan <- struct{}{}
   202  	}
   203  
   204  	// We don't expect the target to be found yet.
   205  	select {
   206  	case <-completeChan:
   207  		t.Fatal("Unexpected completeChan receive")
   208  	case <-foundChan:
   209  		t.Fatal("Unexpected foundChan receive")
   210  	case <-time.After(10 * time.Millisecond):
   211  	}
   212  
   213  	// Cancel the search.
   214  	close(cancelChan)
   215  
   216  	// The next cfilter may have been requested already, so we allow it to
   217  	// send (or wait a bit to make sure it wasn't requested).
   218  	select {
   219  	case tc.chain.sendNextCfilterChan <- struct{}{}:
   220  	case <-time.After(10 * time.Millisecond):
   221  	}
   222  
   223  	// We don't expect neither the complete chan, foundChan or new requests
   224  	// for cfilters to be triggered.
   225  	select {
   226  	case <-tc.chain.sendNextCfilterChan:
   227  		t.Fatal("Unexpected sendNextCfilterChan receive")
   228  	case <-completeChan:
   229  		t.Fatal("Unexpected completeChan receive")
   230  	case <-foundChan:
   231  		t.Fatal("Unexpected foundChan receive")
   232  	default:
   233  	}
   234  }
   235  
   236  // TestHistoricalStartHeight tests that starting the search after the height
   237  // where the target is found makes it actually not get found.
   238  func TestHistoricalStartHeight(t *testing.T) {
   239  	completeChan := make(chan struct{})
   240  	foundChan := make(chan Event)
   241  	tc := newHistTestCtx(t)
   242  	defer tc.cleanup()
   243  
   244  	// The generated test chain is:
   245  	// - 5 blocks that miss the cfilter match
   246  	// - 5 blocks with a cfilter match
   247  	// - block with test case manglers
   248  	// - 5 blocks with a cfilter match
   249  	tc.genBlocks(5, false)
   250  	tc.genBlocks(5, true)
   251  	b := tc.chain.newFromTip(
   252  		confirmScript(testPkScript),
   253  		cfilterData(testPkScript),
   254  	)
   255  	tc.chain.extend(b)
   256  	tc.genBlocks(5, true)
   257  
   258  	// Start the search.
   259  	tc.hist.Find(
   260  		ConfirmedScript(0, testPkScript),
   261  		WithFoundChan(foundChan),
   262  		WithCompleteChan(completeChan),
   263  		WithStartHeight(12),
   264  	)
   265  
   266  	// The completeChan should be signalled with the completion of the
   267  	// scan.
   268  	assertCompleted(t, completeChan)
   269  
   270  	// foundChan should not have been triggered.
   271  	assertFoundChanEmpty(t, foundChan)
   272  }
   273  
   274  // TestHistoricalEndHeight tests that stopping the search before the height
   275  // where the target is found makes it actually not get found.
   276  func TestHistoricalEndHeight(t *testing.T) {
   277  	completeChan := make(chan struct{})
   278  	foundChan := make(chan Event)
   279  	tc := newHistTestCtx(t)
   280  	defer tc.cleanup()
   281  
   282  	// The generated test chain is:
   283  	// - 5 blocks that miss the cfilter match
   284  	// - 5 blocks with a cfilter match
   285  	// - block with test case manglers
   286  	// - 5 blocks with a cfilter match
   287  	tc.genBlocks(5, false)
   288  	tc.genBlocks(5, true)
   289  	b := tc.chain.newFromTip(
   290  		confirmScript(testPkScript),
   291  		cfilterData(testPkScript),
   292  	)
   293  	tc.chain.extend(b)
   294  	tc.genBlocks(5, true)
   295  
   296  	// Start the search.
   297  	tc.hist.Find(
   298  		ConfirmedScript(0, testPkScript),
   299  		WithFoundChan(foundChan),
   300  		WithCompleteChan(completeChan),
   301  		WithEndHeight(int32(b.block.Header.Height-2)),
   302  	)
   303  
   304  	// The completeChan should be signalled with the completion of the
   305  	// scan.
   306  	assertCompleted(t, completeChan)
   307  
   308  	// foundChan should not have been triggered.
   309  	assertFoundChanEmpty(t, foundChan)
   310  }
   311  
   312  // TestHistoricalMultipleMatchesInBlock tests that the historical search
   313  // correctly sends multiple events when the same script is confirmed multiple
   314  // times in a single block.
   315  func TestHistoricalMultipleMatchesInBlock(t *testing.T) {
   316  	completeChan := make(chan struct{})
   317  	foundChan := make(chan Event)
   318  	tc := newHistTestCtx(t)
   319  	defer tc.cleanup()
   320  
   321  	// The generated test chain is:
   322  	// - 5 blocks that miss the cfilter match
   323  	// - 5 blocks with a cfilter match
   324  	// - block with test case manglers
   325  	// - 5 blocks with a cfilter match
   326  	tc.genBlocks(5, false)
   327  	tc.genBlocks(5, true)
   328  	b := tc.chain.newFromTip(
   329  		confirmScript(testPkScript),
   330  		cfilterData(testPkScript),
   331  	)
   332  	// Create an additional output.
   333  	b.block.Transactions[0].AddTxOut(&wire.TxOut{PkScript: testPkScript})
   334  
   335  	tc.chain.extend(b)
   336  	tc.genBlocks(5, true)
   337  
   338  	// Start the search.
   339  	tc.hist.Find(
   340  		ConfirmedScript(0, testPkScript),
   341  		WithFoundChan(foundChan),
   342  		WithCompleteChan(completeChan),
   343  	)
   344  
   345  	// The completeChan should be signalled with the completion of the
   346  	// scan.
   347  	assertCompleted(t, completeChan)
   348  
   349  	// foundChan should be triggered two (and only two) times.
   350  	e1 := assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height))
   351  	e2 := assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height))
   352  	assertFoundChanEmpty(t, foundChan)
   353  
   354  	// However the events should *not* be exactly the same: the script was
   355  	// confirmed in two different outputs.
   356  	if e1 == e2 {
   357  		t.Fatal("script confirmed twice in the same output")
   358  	}
   359  }
   360  
   361  // TestHistoricalBlockDownload tests that the historical search only downloads
   362  // blocks for which the cfilter has passed.
   363  func TestHistoricalBlockDownload(t *testing.T) {
   364  	completeChan := make(chan struct{})
   365  	tc := newHistTestCtx(t)
   366  	defer tc.cleanup()
   367  
   368  	// The generated test chain is:
   369  	// - 5 blocks that miss the cfilter match
   370  	// - 5 blocks with a cfilter match
   371  	// - block with test case manglers
   372  	// - 5 blocks with a cfilter match
   373  	tc.genBlocks(5, false)
   374  	tc.genBlocks(5, true)
   375  	b := tc.chain.newFromTip(
   376  		confirmScript(testPkScript),
   377  		cfilterData(testPkScript),
   378  	)
   379  	tc.chain.extend(b)
   380  	tc.genBlocks(5, true)
   381  
   382  	// Perform the full search.
   383  	tc.hist.Find(
   384  		ConfirmedScript(0, testPkScript),
   385  		WithCompleteChan(completeChan),
   386  	)
   387  	assertCompleted(t, completeChan)
   388  
   389  	// We only expect fetches for 11 blocks of data.
   390  	wantGetBlockCount := uint32(11)
   391  	if tc.chain.getBlockCount != wantGetBlockCount {
   392  		t.Fatalf("Unexpected getBlockCount. want=%d got=%d",
   393  			wantGetBlockCount, tc.chain.getBlockCount)
   394  	}
   395  }
   396  
   397  // TestHistoricalMultipleFinds tests that performing a search with multiple
   398  // finds for the same target works as expected.
   399  func TestHistoricalMultipleFinds(t *testing.T) {
   400  
   401  	runTC := func(c scannerTestCase, t *testing.T) {
   402  		tc := newHistTestCtx(t)
   403  		defer tc.cleanup()
   404  
   405  		// The generated test chain is:
   406  		// - 5 blocks that miss the cfilter match
   407  		// - 5 blocks with a cfilter match
   408  		// - block with test case manglers
   409  		// - 5 blocks with a cfilter match
   410  		tc.genBlocks(5, false)
   411  		tc.genBlocks(5, true)
   412  		b := tc.chain.newFromTip(c.manglers...)
   413  		tc.chain.extend(b)
   414  		tc.genBlocks(5, true)
   415  
   416  		foundChan1 := make(chan Event)
   417  		foundChan2 := make(chan Event)
   418  
   419  		// Start the search.
   420  		tc.hist.FindMany([]TargetAndOptions{
   421  			{
   422  				Target: c.target(b),
   423  				Options: []Option{
   424  					WithFoundChan(foundChan1),
   425  				},
   426  			},
   427  			{
   428  				Target: c.target(b),
   429  				Options: []Option{
   430  					WithFoundChan(foundChan2),
   431  				},
   432  			},
   433  		})
   434  
   435  		// We expect one (and only one) event in each foundChan.
   436  		assertFoundChanRcvHeight(tc.t, foundChan1, int32(b.block.Header.Height))
   437  		assertFoundChanRcvHeight(tc.t, foundChan2, int32(b.block.Header.Height))
   438  		assertFoundChanEmpty(tc.t, foundChan1)
   439  		assertFoundChanEmpty(tc.t, foundChan2)
   440  	}
   441  
   442  	// Test against all variants of targets.
   443  	for _, c := range scannerTestCases {
   444  		if !c.wantFound {
   445  			continue
   446  		}
   447  		c := c
   448  		ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) })
   449  		if !ok {
   450  			break
   451  		}
   452  	}
   453  }
   454  
   455  // TestHistoricalMultipleOverlap tests that starting a search with multiple
   456  // targets with overlapping search intervals works as expected.
   457  func TestHistoricalMultipleOverlap(t *testing.T) {
   458  
   459  	runTC := func(c scannerTestCase, t *testing.T) {
   460  		tc := newHistTestCtx(t)
   461  		defer tc.cleanup()
   462  
   463  		// The generated test chain is:
   464  		// - 5 blocks that miss the cfilter match
   465  		// - 5 blocks with a cfilter match
   466  		// - block with test case manglers
   467  		// - 5 blocks with a cfilter match
   468  		// - block with test case manglers
   469  		tc.genBlocks(5, false)
   470  		tc.genBlocks(5, true)
   471  		b := tc.chain.newFromTip(c.manglers...)
   472  		tc.chain.extend(b)
   473  		tc.genBlocks(5, true)
   474  		b2 := tc.chain.newFromTip(c.manglers...)
   475  		tc.chain.extend(b2)
   476  
   477  		foundChan1 := make(chan Event)
   478  		foundChan2 := make(chan Event)
   479  		completeChan1 := make(chan struct{})
   480  		completeChan2 := make(chan struct{})
   481  
   482  		// Start two concurrent searches with non-overlapping heights.
   483  		tc.hist.FindMany([]TargetAndOptions{
   484  			{
   485  				Target: c.target(b),
   486  				Options: []Option{
   487  					WithFoundChan(foundChan1),
   488  					WithCompleteChan(completeChan1),
   489  					WithEndHeight(int32(b.block.Header.Height + 2)),
   490  				},
   491  			},
   492  			{
   493  				Target: c.target(b2),
   494  				Options: []Option{
   495  					WithFoundChan(foundChan2),
   496  					WithCompleteChan(completeChan2),
   497  					WithStartHeight(int32(b.block.Header.Height + 1)),
   498  				},
   499  			},
   500  		})
   501  
   502  		// Wait for both searches to complete.
   503  		assertCompleted(tc.t, completeChan1)
   504  		assertCompleted(tc.t, completeChan2)
   505  
   506  		// We expect one (and only one) signall in each foundChan.
   507  		assertFoundChanRcvHeight(tc.t, foundChan1, int32(b.block.Header.Height))
   508  		assertFoundChanRcvHeight(tc.t, foundChan2, int32(b2.block.Header.Height))
   509  		assertFoundChanEmpty(tc.t, foundChan1)
   510  		assertFoundChanEmpty(tc.t, foundChan2)
   511  	}
   512  
   513  	// Test against all variants of targets.
   514  	for _, c := range scannerTestCases {
   515  		if !c.wantFound {
   516  			continue
   517  		}
   518  		c := c
   519  		ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) })
   520  		if !ok {
   521  			break
   522  		}
   523  	}
   524  }
   525  
   526  // TestHistoricalMultipleNoOverlap tests that starting a search with multiple,
   527  // non-overlapping targets works as expected.
   528  func TestHistoricalMultipleNoOverlap(t *testing.T) {
   529  
   530  	runTC := func(c scannerTestCase, t *testing.T) {
   531  		tc := newHistTestCtx(t)
   532  		defer tc.cleanup()
   533  
   534  		// The generated test chain is:
   535  		// - 5 blocks that miss the cfilter match
   536  		// - 5 blocks with a cfilter match
   537  		// - block with test case manglers
   538  		// - 5 blocks with a cfilter match
   539  		// - block with test case manglers
   540  		tc.genBlocks(5, false)
   541  		tc.genBlocks(5, true)
   542  		b := tc.chain.newFromTip(c.manglers...)
   543  		tc.chain.extend(b)
   544  		tc.genBlocks(5, true)
   545  		b2 := tc.chain.newFromTip(c.manglers...)
   546  		tc.chain.extend(b2)
   547  
   548  		foundChan1 := make(chan Event)
   549  		foundChan2 := make(chan Event)
   550  		completeChan1 := make(chan struct{})
   551  		completeChan2 := make(chan struct{})
   552  
   553  		// Start two concurrent searches with non-overlapping heights.
   554  		tc.hist.FindMany([]TargetAndOptions{
   555  			{
   556  				Target: c.target(b),
   557  				Options: []Option{
   558  					WithFoundChan(foundChan1),
   559  					WithCompleteChan(completeChan1),
   560  					WithEndHeight(int32(b.block.Header.Height + 1)),
   561  				},
   562  			},
   563  			{
   564  				Target: c.target(b2),
   565  				Options: []Option{
   566  					WithFoundChan(foundChan2),
   567  					WithCompleteChan(completeChan2),
   568  					WithStartHeight(int32(b.block.Header.Height + 4)),
   569  				},
   570  			},
   571  		})
   572  
   573  		// Wait for both searches to complete.
   574  		assertCompleted(tc.t, completeChan1)
   575  		assertCompleted(tc.t, completeChan2)
   576  
   577  		// We expect one (and only one) signall in each foundChan.
   578  		assertFoundChanRcvHeight(tc.t, foundChan1, int32(b.block.Header.Height))
   579  		assertFoundChanRcvHeight(tc.t, foundChan2, int32(b2.block.Header.Height))
   580  		assertFoundChanEmpty(tc.t, foundChan1)
   581  		assertFoundChanEmpty(tc.t, foundChan2)
   582  	}
   583  
   584  	// Test against all variants of targets.
   585  	for _, c := range scannerTestCases {
   586  		if !c.wantFound {
   587  			continue
   588  		}
   589  		c := c
   590  		ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) })
   591  		if !ok {
   592  			break
   593  		}
   594  	}
   595  }
   596  
   597  // TestHistoricalAddNewTargetDuringFcb tests that adding new targets for the
   598  // historical search during the call for FoundCallback by using the passed
   599  // function works as expected and provokes the new target to be found.
   600  func TestHistoricalAddNewTargetDuringFcb(t *testing.T) {
   601  
   602  	runTC := func(c scannerTestCase, t *testing.T) {
   603  		tc := newHistTestCtx(t)
   604  		defer tc.cleanup()
   605  
   606  		// The generated test chain is:
   607  		// - 5 blocks that miss the cfilter match
   608  		// - 5 blocks with a cfilter match
   609  		// - block with test case manglers (twice)
   610  		// - 5 blocks with a cfilter match
   611  		tc.genBlocks(5, false)
   612  		tc.genBlocks(5, true)
   613  		b := tc.chain.newFromTip(c.manglers...)
   614  		dupeTestTx(b)
   615  		tc.chain.extend(b)
   616  		tc.genBlocks(5, true)
   617  
   618  		foundChan := make(chan Event)
   619  		foundCb := func(e Event, addNew FindFunc) {
   620  			assertNoError(t, addNew(
   621  				c.target(b),
   622  				WithStartHeight(e.BlockHeight+1),
   623  				WithFoundChan(foundChan),
   624  			))
   625  		}
   626  
   627  		// Start the search.
   628  		tc.hist.Find(
   629  			c.target(b),
   630  			WithFoundCallback(foundCb),
   631  		)
   632  
   633  		// We expect one (and only one) event in foundChan
   634  		assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height))
   635  		assertFoundChanEmpty(t, foundChan)
   636  
   637  	}
   638  	// Test against all variants of targets.
   639  	for _, c := range scannerTestCases {
   640  		if !c.wantFound {
   641  			continue
   642  		}
   643  		c := c
   644  		ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) })
   645  		if !ok {
   646  			break
   647  		}
   648  	}
   649  }
   650  
   651  // TestHistoricalAddNewTarget tests that adding new targets for the historical
   652  // search during the call for FoundCallback works as expected and provokes the
   653  // new target to be found.
   654  func TestHistoricalAddNewTarget(t *testing.T) {
   655  	tc := newHistTestCtx(t)
   656  	defer tc.cleanup()
   657  
   658  	pkScript2 := []byte{0x01, 0x02, 0x03, 0x04}
   659  
   660  	// The generated test chain is:
   661  	// - 5 blocks that miss the cfilter match
   662  	// - 5 blocks with a cfilter match
   663  	// - block with test case manglers
   664  	// - 5 blocks with a cfilter match
   665  	// - block with second confirmed pkscript
   666  	tc.genBlocks(5, false)
   667  	tc.genBlocks(5, true)
   668  	b := tc.chain.newFromTip(
   669  		confirmScript(testPkScript),
   670  		cfilterData(testPkScript),
   671  	)
   672  	tc.chain.extend(b)
   673  	tc.genBlocks(5, true)
   674  	b2 := tc.chain.newFromTip(
   675  		confirmScript(pkScript2),
   676  		cfilterData(pkScript2),
   677  	)
   678  	tc.chain.extend(b2)
   679  
   680  	foundChanOld := make(chan Event)
   681  	foundChanNew := make(chan Event)
   682  	foundChanRestart := make(chan Event)
   683  	foundCb := func(e Event, _ FindFunc) {
   684  		// The foundCallback will start a new search for three
   685  		// different targets:
   686  		// - The testPkScript with startHeight of e.BlockHeight+1 which
   687  		// shouldn't match;
   688  		// - The pkScript2 with startHeight of e.BlockHeight+1 which
   689  		// should match;
   690  		// - The testPkScript with startHeight of 0 which should match.
   691  		tc.hist.Find(
   692  			ConfirmedScript(0, testPkScript),
   693  			WithFoundChan(foundChanOld),
   694  			WithStartHeight(e.BlockHeight+1),
   695  		)
   696  		tc.hist.Find(
   697  			ConfirmedScript(0, pkScript2),
   698  			WithFoundChan(foundChanNew),
   699  			WithStartHeight(e.BlockHeight+1),
   700  		)
   701  		tc.hist.Find(
   702  			ConfirmedScript(0, testPkScript),
   703  			WithFoundChan(foundChanRestart),
   704  		)
   705  	}
   706  
   707  	// Start the search.
   708  	tc.hist.Find(
   709  		ConfirmedScript(0, testPkScript),
   710  		WithFoundCallback(foundCb),
   711  	)
   712  
   713  	// foundChanOld should be empty since the search was started at a block
   714  	// height higher than where the script was confirmed. The other two
   715  	// channels should have confirmations.
   716  	assertFoundChanEmpty(t, foundChanOld)
   717  	assertFoundChanRcvHeight(t, foundChanNew, int32(b2.block.Header.Height))
   718  	assertFoundChanRcvHeight(t, foundChanRestart, int32(b.block.Header.Height))
   719  }
   720  
   721  // TestHistoricalAddNewTargetSingleBatch tests that adding new targets for the
   722  // historical search during the call for FoundCallback which doesn't lead to a
   723  // new batch correctly causes only the current batch to be executed.
   724  func TestHistoricalAddNewTargetSingleBatch(t *testing.T) {
   725  	tc := newHistTestCtx(t)
   726  	defer tc.cleanup()
   727  
   728  	// The generated test chain is:
   729  	// - 5 blocks that miss the cfilter match
   730  	// - 5 blocks with a cfilter match
   731  	// - block with test case manglers
   732  	// - 5 blocks with a cfilter match
   733  	// - block with second confirmed pkscript
   734  	tc.genBlocks(5, false)
   735  	tc.genBlocks(5, true)
   736  	b := tc.chain.newFromTip(
   737  		confirmScript(testPkScript),
   738  		cfilterData(testPkScript),
   739  	)
   740  	tc.chain.extend(b)
   741  	tc.genBlocks(5, true)
   742  
   743  	completeChan := make(chan struct{})
   744  	foundCb := func(e Event, _ FindFunc) {
   745  		// Add the new target with a start height of currentHeight+1 so
   746  		// that it will be added to the current batch.
   747  		tc.hist.Find(
   748  			ConfirmedScript(0, testPkScript),
   749  			WithCompleteChan(completeChan),
   750  			WithStartHeight(e.BlockHeight+1),
   751  		)
   752  	}
   753  
   754  	// Start the search.
   755  	tc.hist.Find(
   756  		ConfirmedScript(0, testPkScript),
   757  		WithFoundCallback(foundCb),
   758  	)
   759  
   760  	assertCompleted(t, completeChan)
   761  
   762  	// We expect only a single batch to have taken place, so all blocks
   763  	// should have been downloaded only once.
   764  	wantGetBlockCount := uint32(11)
   765  	if tc.chain.getBlockCount != wantGetBlockCount {
   766  		t.Fatalf("Unexpected getBlockCount. want=%d got=%d",
   767  			wantGetBlockCount, tc.chain.getBlockCount)
   768  	}
   769  }
   770  
   771  // TestHistoricalNewTipNoOverlap tests that performing a historical search when
   772  // new tips of the chain are coming in behaves as expected when we create a new
   773  // search that does not overlap with the existing search.
   774  func TestHistoricalNewTipNoOverlap(t *testing.T) {
   775  	tc := newHistTestCtx(t)
   776  	defer tc.cleanup()
   777  
   778  	// Instrument the mock chain so we can stop the search half-way
   779  	// through.
   780  	tc.chain.sendNextCfilterChan = make(chan struct{})
   781  
   782  	// The generated test chain is:
   783  	// - 5 blocks that miss the cfilter match
   784  	// - 5 blocks with a cfilter match
   785  	// - block with test case manglers
   786  	// - 5 blocks with a cfilter match
   787  	// - block with second confirmed pkscript
   788  	tc.genBlocks(5, false)
   789  	tc.genBlocks(5, true)
   790  	b := tc.chain.newFromTip(
   791  		confirmScript(testPkScript),
   792  		cfilterData(testPkScript),
   793  	)
   794  	tc.chain.extend(b)
   795  	tc.genBlocks(5, true)
   796  
   797  	// Start the search.
   798  	foundChan := make(chan Event)
   799  	tc.hist.Find(
   800  		ConfirmedScript(0, testPkScript),
   801  		WithFoundChan(foundChan),
   802  	)
   803  
   804  	// Let it process 3 blocks and start processing the fourth.
   805  	tc.chain.sendNextCfilterChan <- struct{}{}
   806  	tc.chain.sendNextCfilterChan <- struct{}{}
   807  	tc.chain.sendNextCfilterChan <- struct{}{}
   808  
   809  	// Let the blocks be processed.
   810  	time.Sleep(10 * time.Millisecond)
   811  
   812  	// Extend the tip with 3 new blocks, including a match of the target at
   813  	// the end.
   814  	startHeight := int32(tc.chain.tip.block.Header.Height) + 1
   815  	tc.genBlocks(2, true)
   816  	b2 := tc.chain.newFromTip(
   817  		confirmScript(testPkScript),
   818  		cfilterData(testPkScript),
   819  	)
   820  	tc.chain.extend(b2)
   821  
   822  	// Start a new search which does not overlap with the previous search.
   823  	// The start height will be higher than the previous search's end
   824  	// height.
   825  	foundChanNew := make(chan Event)
   826  	completeChanNew := make(chan struct{})
   827  	tc.hist.Find(
   828  		ConfirmedScript(0, testPkScript),
   829  		WithFoundChan(foundChanNew),
   830  		WithStartHeight(startHeight),
   831  		WithCompleteChan(completeChanNew),
   832  	)
   833  
   834  	// Send as many blocks as needed until no more are requested by the
   835  	// scan.
   836  	done := false
   837  	for !done {
   838  		select {
   839  		case tc.chain.sendNextCfilterChan <- struct{}{}:
   840  		case <-time.After(10 * time.Millisecond):
   841  			done = true
   842  		}
   843  	}
   844  
   845  	// Wait for the second scan to wrap up.
   846  	assertCompleted(tc.t, completeChanNew)
   847  
   848  	// We expect one (and only one) signal in each foundChan.
   849  	assertFoundChanRcvHeight(tc.t, foundChan, int32(b.block.Header.Height))
   850  	assertFoundChanRcvHeight(tc.t, foundChanNew, int32(b2.block.Header.Height))
   851  	assertFoundChanEmpty(tc.t, foundChan)
   852  	assertFoundChanEmpty(tc.t, foundChanNew)
   853  }
   854  
   855  // TestHistoricalNewTipOverlap tests that performing a historical search when
   856  // new tips of the chain are coming in behaves as expected when we create a new
   857  // search that overlaps with the existing search.
   858  func TestHistoricalNewTipOverlap(t *testing.T) {
   859  	tc := newHistTestCtx(t)
   860  	defer tc.cleanup()
   861  
   862  	// Instrument the mock chain so we can stop the search mid-way through.
   863  	tc.chain.sendNextCfilterChan = make(chan struct{})
   864  
   865  	// The generated test chain is:
   866  	// - 5 blocks that miss the cfilter match
   867  	// - 5 blocks with a cfilter match
   868  	// - block with test case manglers
   869  	// - 5 blocks with a cfilter match
   870  	// - block with second confirmed pkscript
   871  	tc.genBlocks(5, false)
   872  	tc.genBlocks(5, true)
   873  	b := tc.chain.newFromTip(
   874  		confirmScript(testPkScript),
   875  		cfilterData(testPkScript),
   876  	)
   877  	tc.chain.extend(b)
   878  	tc.genBlocks(5, true)
   879  
   880  	// Start the search.
   881  	foundChan := make(chan Event)
   882  	tc.hist.Find(
   883  		ConfirmedScript(0, testPkScript),
   884  		WithFoundChan(foundChan),
   885  	)
   886  
   887  	// Let it process 3 blocks and start processing the fourth.
   888  	tc.chain.sendNextCfilterChan <- struct{}{}
   889  	tc.chain.sendNextCfilterChan <- struct{}{}
   890  	tc.chain.sendNextCfilterChan <- struct{}{}
   891  
   892  	// Let the blocks be processed.
   893  	time.Sleep(10 * time.Millisecond)
   894  
   895  	// Extend the tip with 3 new blocks, including a match of the target at
   896  	// the end.
   897  	startHeight := int32(13)
   898  	tc.genBlocks(2, true)
   899  	b2 := tc.chain.newFromTip(
   900  		confirmScript(testPkScript),
   901  		cfilterData(testPkScript),
   902  	)
   903  	tc.chain.extend(b2)
   904  
   905  	// Start a new search which overlaps with the previous search. The
   906  	// start height will be lower than the previous search's end height and
   907  	// its current height.
   908  	foundChanNew := make(chan Event)
   909  	completeChanNew := make(chan struct{})
   910  	tc.hist.Find(
   911  		ConfirmedScript(0, testPkScript),
   912  		WithFoundChan(foundChanNew),
   913  		WithStartHeight(startHeight),
   914  		WithCompleteChan(completeChanNew),
   915  	)
   916  
   917  	// Send as many blocks as needed until no more are requested by the
   918  	// scan.
   919  	done := false
   920  	for !done {
   921  		select {
   922  		case tc.chain.sendNextCfilterChan <- struct{}{}:
   923  		case <-time.After(10 * time.Millisecond):
   924  			done = true
   925  		}
   926  	}
   927  
   928  	// Wait for the second scan to wrap up.
   929  	assertCompleted(tc.t, completeChanNew)
   930  
   931  	// We expect one (and only one) signal in each foundChan.
   932  	assertFoundChanRcvHeight(tc.t, foundChan, int32(b.block.Header.Height))
   933  	assertFoundChanRcvHeight(tc.t, foundChanNew, int32(b2.block.Header.Height))
   934  	assertFoundChanEmpty(tc.t, foundChan)
   935  	assertFoundChanEmpty(tc.t, foundChanNew)
   936  
   937  	// We only expect one fetch for each (cfilter matched) block.
   938  	wantGetBlockCount := uint32(14)
   939  	if tc.chain.getBlockCount != wantGetBlockCount {
   940  		t.Fatalf("Unexpected getBlockCount. want=%d got=%d",
   941  			wantGetBlockCount, tc.chain.getBlockCount)
   942  	}
   943  }
   944  
   945  // TestHistoricalAfterTip tests that attempting to scan past the current chain
   946  // tip works as expected.
   947  func TestHistoricalAfterTip(t *testing.T) {
   948  	tc := newHistTestCtx(t)
   949  	defer tc.cleanup()
   950  
   951  	// The generated test chain is:
   952  	// - 5 blocks that miss the cfilter match
   953  	// - 5 blocks with a cfilter match
   954  	// - block with test case manglers
   955  	// - 5 blocks with a cfilter match
   956  	// - block with second confirmed pkscript
   957  	tc.genBlocks(5, false)
   958  	tc.genBlocks(5, true)
   959  	b := tc.chain.newFromTip(
   960  		confirmScript(testPkScript),
   961  		cfilterData(testPkScript),
   962  	)
   963  	tc.chain.extend(b)
   964  	tc.genBlocks(5, true)
   965  
   966  	completeChan := make(chan struct{})
   967  	foundChan := make(chan Event)
   968  
   969  	// Start the search with an EndHeight past the tip. The mock chain
   970  	// returns ErrBlockAfterTip in this situation.
   971  	tc.hist.Find(
   972  		ConfirmedScript(0, testPkScript),
   973  		WithFoundChan(foundChan),
   974  		WithCompleteChan(completeChan),
   975  		WithEndHeight(int32(tc.chain.tip.block.Header.Height)+1),
   976  	)
   977  
   978  	assertCompleted(t, completeChan)
   979  	assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height))
   980  }
   981  
   982  // TestHistoricalFindSpendAfterConfirm asserts that attempting to find a spent
   983  // UTXO works when the spend happens on a later block and the spent is added
   984  // during the search for confirmation.
   985  func TestHistoricalFindSpendAfterConfirm(t *testing.T) {
   986  	tc := newHistTestCtx(t)
   987  	defer tc.cleanup()
   988  
   989  	// The generated test chain is:
   990  	// - 5 blocks that miss the cfilter match
   991  	// - 5 blocks with a cfilter match
   992  	// - block with confirmed output
   993  	// - 5 blocks with a cfilter match
   994  	// - 5 blocks that miss the cfilter match
   995  	// - block which spends the previous output
   996  	tc.genBlocks(5, false)
   997  	tc.genBlocks(5, true)
   998  	bConfirm := tc.chain.newFromTip(
   999  		confirmScript(testPkScript),
  1000  		cfilterData(testPkScript),
  1001  	)
  1002  	outp := wire.OutPoint{
  1003  		Hash:  bConfirm.block.Transactions[0].TxHash(),
  1004  		Index: 1,
  1005  	}
  1006  	tc.chain.extend(bConfirm)
  1007  	tc.genBlocks(5, true)
  1008  	tc.genBlocks(5, false)
  1009  	bSpend := tc.chain.newFromTip(
  1010  		spendOutPoint(outp),
  1011  		cfilterData(testPkScript),
  1012  	)
  1013  	tc.chain.extend(bSpend)
  1014  
  1015  	// Setup the callback that is called when the output is confirmed and
  1016  	// which will trigger the search for the spent outpoint.
  1017  	foundSpentChan := make(chan Event)
  1018  	completeChan := make(chan struct{})
  1019  	foundCb := func(e Event, addNew FindFunc) {
  1020  		assertNoError(t, addNew(
  1021  			SpentOutPoint(outp, 0, testPkScript),
  1022  			WithStartHeight(e.BlockHeight+1),
  1023  			WithFoundChan(foundSpentChan),
  1024  			WithCompleteChan(completeChan),
  1025  		))
  1026  	}
  1027  
  1028  	// Start the search.
  1029  	tc.hist.Find(
  1030  		ConfirmedScript(0, testPkScript),
  1031  		WithFoundCallback(foundCb),
  1032  	)
  1033  
  1034  	// Search should've been completed and the spending tx found.
  1035  	assertCompleted(t, completeChan)
  1036  	assertFoundChanRcvHeight(t, foundSpentChan, int32(bSpend.block.Header.Height))
  1037  }
  1038  
  1039  // BenchHistoricalCfilterMisses benchmarks the behavior of historical searches
  1040  // when most/all blocks cause a cfilter check to miss (that is, full blocks
  1041  // aren't downloaded and tested individually).
  1042  //
  1043  // Reported time and allocation count should be interpreted as per-block in the
  1044  // blockchain.
  1045  func BenchmarkHistoricalCfilterMisses(b *testing.B) {
  1046  
  1047  	runTC := func(c scannerTestCase, b *testing.B) {
  1048  		b.ReportAllocs()
  1049  		tc := newHistTestCtx(b)
  1050  		defer tc.cleanup()
  1051  
  1052  		// Dummy block (will never match as we won't add it to the
  1053  		// chain).
  1054  		bl := tc.chain.newFromTip(c.manglers...)
  1055  
  1056  		// Generate N blocks without cfilter matches.
  1057  		tc.genBlocks(b.N, false)
  1058  		completeChan := make(chan struct{})
  1059  		foundChan := make(chan Event)
  1060  
  1061  		targets := make([]TargetAndOptions, 1)
  1062  		targets[0] = TargetAndOptions{
  1063  			Target: c.target(bl),
  1064  			Options: []Option{
  1065  				WithCompleteChan(completeChan),
  1066  				WithFoundChan(foundChan),
  1067  			},
  1068  		}
  1069  
  1070  		// Reset the benchmark to this point.
  1071  		b.ResetTimer()
  1072  
  1073  		// Find all items and wait for them to complete.
  1074  		tc.hist.FindMany(targets)
  1075  		select {
  1076  		case <-foundChan:
  1077  			b.Fatal("Unexpected event in foundChan")
  1078  		case <-completeChan:
  1079  		case <-time.After(60 * time.Second):
  1080  			b.Fatal("Timeout in benchmark")
  1081  		}
  1082  	}
  1083  
  1084  	// Test against all variants of targets.
  1085  	for _, c := range scannerTestCases {
  1086  		if !c.wantFound {
  1087  			continue
  1088  		}
  1089  		c := c
  1090  		ok := b.Run(c.name, func(b *testing.B) { runTC(c, b) })
  1091  		if !ok {
  1092  			break
  1093  		}
  1094  	}
  1095  }
  1096  
  1097  // BenchHistoricalMatches benchmarks the behavior of historical searches when
  1098  // most/all blocks cause a match in the block itself.
  1099  //
  1100  // Reported time and allocation counts should be interpreted as per-(1 input +
  1101  // 1 output) in the blockchain.
  1102  //
  1103  // Note: This benchmark only runs for testcases which might match multiple
  1104  // blocks (i.e., only match by script vs outpoint).
  1105  func BenchmarkHistoricalMatches(b *testing.B) {
  1106  
  1107  	runTC := func(c scannerTestCase, b *testing.B) {
  1108  		b.ReportAllocs()
  1109  		tc := newHistTestCtx(b)
  1110  		defer tc.cleanup()
  1111  
  1112  		bl := tc.chain.newFromTip(c.manglers...)
  1113  
  1114  		// Generate N blocks with block matches.
  1115  		tc.chain.genBlocks(b.N, c.manglers...)
  1116  		completeChan := make(chan struct{})
  1117  		foundChan := make(chan Event)
  1118  		var cbCount int
  1119  		foundCb := func(_ Event, _ FindFunc) {
  1120  			cbCount++
  1121  		}
  1122  
  1123  		// Drain foundChan until completeChan is closed.
  1124  		go func() {
  1125  			for {
  1126  				select {
  1127  				case <-foundChan:
  1128  				case <-completeChan:
  1129  					return
  1130  				}
  1131  			}
  1132  		}()
  1133  
  1134  		targets := make([]TargetAndOptions, 1)
  1135  		targets[0] = TargetAndOptions{
  1136  			Target: c.target(bl),
  1137  			Options: []Option{
  1138  				WithCompleteChan(completeChan),
  1139  				WithFoundCallback(foundCb),
  1140  				WithFoundChan(foundChan),
  1141  			},
  1142  		}
  1143  
  1144  		// Reset the benchmark to this point.
  1145  		b.ResetTimer()
  1146  
  1147  		// Find all items and wait for them to complete.
  1148  		tc.hist.FindMany(targets)
  1149  		select {
  1150  		case <-completeChan:
  1151  			/*
  1152  				if cbCount != b.N {
  1153  					b.Fatalf("Different number of callback calls. want=%d got=%d",
  1154  						b.N, cbCount)
  1155  				}
  1156  			*/
  1157  		case <-time.After(60 * time.Second):
  1158  			b.Fatal("Timeout in benchmark")
  1159  		}
  1160  	}
  1161  
  1162  	// Test against all variants of targets.
  1163  	for _, c := range scannerTestCases {
  1164  		// The only test cases amenable to this benchmark are those
  1165  		// that use a fixed script or outpoint for matching.
  1166  		//
  1167  		// Returning multiple matches against a specific spent outpoint
  1168  		// isn't really something that should happen in the blockchain
  1169  		// but is useful as a benchmark method.
  1170  		if c.name != "SpentScript" && c.name != "ConfirmedScript" && c.name != "SpentOutPoint" {
  1171  			continue
  1172  		}
  1173  
  1174  		c := c
  1175  		ok := b.Run(c.name, func(b *testing.B) { runTC(c, b) })
  1176  		if !ok {
  1177  			break
  1178  		}
  1179  	}
  1180  }
  1181  
  1182  // BenchHistorical benchmarks the behavior of historical searches when most/all
  1183  // blocks cause a cfilter check to match (that is, full blocks are downloaded
  1184  // and tested individually).
  1185  //
  1186  // Reported time and allocation should be interpreted as per-(1 input + 1
  1187  // output) in the blockchain.
  1188  func BenchmarkHistorical(b *testing.B) {
  1189  
  1190  	runTC := func(c scannerTestCase, b *testing.B) {
  1191  		b.ReportAllocs()
  1192  		tc := newHistTestCtx(b)
  1193  		defer tc.cleanup()
  1194  
  1195  		// Dummy block (will never match as we won't add it to the
  1196  		// chain).
  1197  		bl := tc.chain.newFromTip(c.manglers...)
  1198  
  1199  		// Generate N blocks with cfilter matches.
  1200  		tc.genBlocks(b.N, true)
  1201  		completeChan := make(chan struct{})
  1202  		foundChan := make(chan Event)
  1203  
  1204  		targets := make([]TargetAndOptions, 1)
  1205  		targets[0] = TargetAndOptions{
  1206  			Target: c.target(bl),
  1207  			Options: []Option{
  1208  				WithCompleteChan(completeChan),
  1209  				WithFoundChan(foundChan),
  1210  			},
  1211  		}
  1212  
  1213  		// Reset the benchmark to this point.
  1214  		b.ResetTimer()
  1215  
  1216  		// Find all items and wait for them to complete.
  1217  		tc.hist.FindMany(targets)
  1218  		select {
  1219  		case <-foundChan:
  1220  			b.Fatal("Unexpected event in foundChan")
  1221  		case <-completeChan:
  1222  		case <-time.After(60 * time.Second):
  1223  			b.Fatal("Timeout in benchmark")
  1224  		}
  1225  	}
  1226  
  1227  	// Test against all variants of targets.
  1228  	for _, c := range scannerTestCases {
  1229  		if !c.wantFound {
  1230  			continue
  1231  		}
  1232  		c := c
  1233  		ok := b.Run(c.name, func(b *testing.B) { runTC(c, b) })
  1234  		if !ok {
  1235  			break
  1236  		}
  1237  	}
  1238  }