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

     1  package chainscan
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/decred/dcrd/wire"
    10  )
    11  
    12  // scannerTestCase is a test case that must be fulfilled by both the historical
    13  // and the tip watcher individually.
    14  type scannerTestCase struct {
    15  	name      string
    16  	target    func(*testBlock) Target
    17  	manglers  []blockMangler
    18  	wantFound bool
    19  	wantMF    MatchField
    20  }
    21  
    22  var scannerTestCases = []scannerTestCase{
    23  
    24  	// Basic tests where a block should match the target.
    25  
    26  	{
    27  		name: "ConfirmedScript",
    28  		target: func(b *testBlock) Target {
    29  			return ConfirmedScript(0, testPkScript)
    30  		},
    31  		manglers: []blockMangler{
    32  			confirmScript(testPkScript),
    33  			cfilterData(testPkScript),
    34  		},
    35  		wantFound: true,
    36  		wantMF:    MatchTxOut,
    37  	},
    38  
    39  	{
    40  		name: "ConfirmedOutPoint",
    41  		target: func(b *testBlock) Target {
    42  			outp := wire.OutPoint{
    43  				Hash:  b.block.Transactions[0].TxHash(),
    44  				Index: 1,
    45  			}
    46  			return ConfirmedOutPoint(outp, 0, testPkScript)
    47  		},
    48  		manglers: []blockMangler{
    49  			confirmScript(testPkScript),
    50  			cfilterData(testPkScript),
    51  		},
    52  		wantFound: true,
    53  		wantMF:    MatchTxOut,
    54  	},
    55  
    56  	{
    57  		name: "SpentScript",
    58  		target: func(b *testBlock) Target {
    59  			return SpentScript(0, testPkScript)
    60  		},
    61  		manglers: []blockMangler{
    62  			spendScript(testSigScript),
    63  			cfilterData(testPkScript),
    64  		},
    65  		wantFound: true,
    66  		wantMF:    MatchTxIn,
    67  	},
    68  
    69  	{
    70  		name: "SpentOutPoint",
    71  		target: func(b *testBlock) Target {
    72  			outp := b.block.Transactions[0].TxIn[1].PreviousOutPoint
    73  			return SpentOutPoint(outp, 0, testPkScript)
    74  		},
    75  		manglers: []blockMangler{
    76  			spendOutPoint(testOutPoint),
    77  			cfilterData(testPkScript),
    78  		},
    79  		wantFound: true,
    80  		wantMF:    MatchTxIn,
    81  	},
    82  
    83  	// This test forces something that would ordinarily match in the block
    84  	// to not be included in the cfilter. This is not a realistic scenario
    85  	// in practice (due to consensus rules enforcing the correct behavior)
    86  	// but shows that if the cfilter doesn't match the block itself isn't
    87  	// tested.
    88  	{
    89  		name: "ConfirmedScript with cfilter miss",
    90  		target: func(b *testBlock) Target {
    91  			return ConfirmedScript(0, testPkScript)
    92  		},
    93  		manglers: []blockMangler{
    94  			confirmScript(testPkScript),
    95  		},
    96  	},
    97  
    98  	// The rest of the tests all force trigger a block check since a
    99  	// cfilter miss is trivial.
   100  
   101  	{
   102  		name: "ConfirmedScript without match",
   103  		target: func(b *testBlock) Target {
   104  			return ConfirmedScript(0, testPkScript)
   105  		},
   106  		manglers: []blockMangler{
   107  			// Note testPkScript is _not_ confirmed
   108  			cfilterData(testPkScript),
   109  		},
   110  	},
   111  
   112  	{
   113  		name: "ConfirmedOutPoint without match",
   114  		target: func(b *testBlock) Target {
   115  			outp := wire.OutPoint{
   116  				Hash:  b.block.Transactions[0].TxHash(),
   117  				Index: 1,
   118  			}
   119  			return ConfirmedOutPoint(outp, 0, testPkScript)
   120  		},
   121  		manglers: []blockMangler{
   122  			cfilterData(testPkScript),
   123  		},
   124  	},
   125  
   126  	// This tests that trying to watch for a specific outpoint fails when
   127  	// the script is confirmed in a _different_ outpoint.
   128  	{
   129  		name: "ConfirmedOutPoint with different outpoint",
   130  		target: func(b *testBlock) Target {
   131  			outp := wire.OutPoint{
   132  				Hash:  b.block.Transactions[0].TxHash(),
   133  				Index: 0,
   134  			}
   135  			return ConfirmedOutPoint(outp, 0, testPkScript)
   136  		},
   137  		manglers: []blockMangler{
   138  			confirmScript(testPkScript),
   139  			cfilterData(testPkScript),
   140  		},
   141  	},
   142  
   143  	{
   144  		name: "SpentScript without match",
   145  		target: func(b *testBlock) Target {
   146  			return SpentScript(0, testPkScript)
   147  		},
   148  		manglers: []blockMangler{
   149  			cfilterData(testPkScript),
   150  		},
   151  	},
   152  
   153  	{
   154  		name: "SpentOutPoint without match",
   155  		target: func(b *testBlock) Target {
   156  			outp := wire.OutPoint{
   157  				Hash:  b.block.Transactions[0].TxHash(),
   158  				Index: 1,
   159  			}
   160  			return SpentOutPoint(outp, 0, testPkScript)
   161  		},
   162  		manglers: []blockMangler{
   163  			cfilterData(testPkScript),
   164  		},
   165  	},
   166  
   167  	// This tests that a match is triggered even when the signatureScript
   168  	// of a watched outpoint does not correspond to the requested pkscript
   169  	// to watch for.
   170  	//
   171  	// Note that this is technically a client error given that watching for
   172  	// an outpoint when its correspondong pkscript is not the one specified
   173  	// in SpentOutpoint might cause the cfilter to never trigger a block
   174  	// download.
   175  	//
   176  	// Nevertheless we provide this test to fixate the TipWatcher's
   177  	// behavior in this situation.
   178  	{
   179  		name: "SpentOutPoint with different script",
   180  		target: func(b *testBlock) Target {
   181  			outp := b.block.Transactions[0].TxIn[1].PreviousOutPoint
   182  			return SpentOutPoint(outp, 0, testPkScript)
   183  		},
   184  		manglers: []blockMangler{
   185  			spendScript([]byte{0x00}),
   186  			cfilterData(testPkScript),
   187  		},
   188  		wantFound: true,
   189  		wantMF:    MatchTxIn,
   190  	},
   191  
   192  	// This asserts that a match is not triggered for a confirmation when
   193  	// the script is spent in a random input.
   194  	{
   195  		name: "ConfirmedScript with spent script",
   196  		target: func(b *testBlock) Target {
   197  			return ConfirmedScript(0, testPkScript)
   198  		},
   199  		manglers: []blockMangler{
   200  			spendScript(testSigScript),
   201  			cfilterData(testPkScript),
   202  		},
   203  	},
   204  
   205  	// The next tests all deal with transactions in the stake tree.
   206  
   207  	// This test asserts that spending an output which is in the stake tree
   208  	// gets detected.
   209  	{
   210  		name: "Spent Stake OutPoint",
   211  		target: func(b *testBlock) Target {
   212  			outp := b.block.Transactions[0].TxIn[1].PreviousOutPoint
   213  			return SpentOutPoint(outp, 0, testPkScript)
   214  		},
   215  		manglers: []blockMangler{
   216  			spendOutPoint(wire.OutPoint{
   217  				Hash: testOutPoint.Hash,
   218  				Tree: wire.TxTreeStake,
   219  			}),
   220  			cfilterData(testPkScript),
   221  		},
   222  		wantFound: true,
   223  		wantMF:    MatchTxIn,
   224  	},
   225  
   226  	{
   227  		name: "ConfirmedOutPoint in stake tx",
   228  		target: func(b *testBlock) Target {
   229  			outp := wire.OutPoint{
   230  				Hash:  b.block.STransactions[0].TxHash(),
   231  				Index: 1,
   232  				Tree:  wire.TxTreeStake,
   233  			}
   234  			return ConfirmedOutPoint(outp, 0, testPkScript)
   235  		},
   236  		manglers: []blockMangler{
   237  			confirmScript(testPkScript),
   238  			cfilterData(testPkScript),
   239  			moveRegularToStakeTree(),
   240  		},
   241  		wantFound: true,
   242  		wantMF:    MatchTxOut,
   243  	},
   244  
   245  	{
   246  		name: "ConfirmedScript in stake tx",
   247  		target: func(b *testBlock) Target {
   248  			return ConfirmedScript(0, testPkScript)
   249  		},
   250  		manglers: []blockMangler{
   251  			confirmScript(testPkScript),
   252  			cfilterData(testPkScript),
   253  			moveRegularToStakeTree(),
   254  		},
   255  		wantFound: true,
   256  		wantMF:    MatchTxOut,
   257  	},
   258  
   259  	// This test ensures that trying to watch for a script which should be
   260  	// confirmed in a specific output in the regular transaction tree fails
   261  	// to trigger a found event when that same script is actually confirmed
   262  	// in the stake tree.
   263  	{
   264  		name: "ConfirmedOutPoint in wrong tx tree",
   265  		target: func(b *testBlock) Target {
   266  			outp := wire.OutPoint{
   267  				Hash:  b.block.STransactions[0].TxHash(),
   268  				Index: 1,
   269  				Tree:  wire.TxTreeRegular,
   270  			}
   271  			return ConfirmedOutPoint(outp, 0, testPkScript)
   272  		},
   273  		manglers: []blockMangler{
   274  			confirmScript(testPkScript),
   275  			cfilterData(testPkScript),
   276  			moveRegularToStakeTree(),
   277  		},
   278  	},
   279  
   280  	{
   281  		name: "ConfirmedScript with large script",
   282  		target: func(b *testBlock) Target {
   283  			return ConfirmedScript(0, bytes.Repeat([]byte{0x55}, 128))
   284  		},
   285  		manglers: []blockMangler{
   286  			confirmScript(bytes.Repeat([]byte{0x55}, 128)),
   287  			cfilterData(bytes.Repeat([]byte{0x55}, 128)),
   288  		},
   289  		wantFound: true,
   290  		wantMF:    MatchTxOut,
   291  	},
   292  
   293  	{
   294  		name: "ConfirmedScript with large script without match",
   295  		target: func(b *testBlock) Target {
   296  			return ConfirmedScript(0, bytes.Repeat([]byte{0x55}, 128))
   297  		},
   298  		manglers: []blockMangler{
   299  			confirmScript(bytes.Repeat([]byte{0x55}, 127)),
   300  			cfilterData(bytes.Repeat([]byte{0x55}, 128)),
   301  		},
   302  	},
   303  }
   304  
   305  // TestSimultaneousScanners tests that when running both a TipWatcher and a
   306  // Historical rescan against the same chain, events are consistent with the
   307  // expected behavior of both scanners.
   308  func TestSimultaneousScanners(t *testing.T) {
   309  	ctx, cancel := context.WithCancel(context.Background())
   310  	defer cancel()
   311  	chain := newMockChain()
   312  	chain.extend(chain.newFromTip()) // Genesis block
   313  
   314  	// The manglers that generate a block with a match.
   315  	confirmManglers := []blockMangler{
   316  		confirmScript(testPkScript),
   317  		cfilterData(testPkScript),
   318  	}
   319  
   320  	// The generated test chain is:
   321  	// - 5 blocks that miss the cfilter match
   322  	// - 5 blocks with a cfilter match
   323  	// - block which confirms the test pkscript
   324  	// - 5 blocks with a cfilter match
   325  	chain.genBlocks(5)
   326  	chain.genBlocks(5, cfilterData(testPkScript))
   327  	b := chain.newFromTip(confirmManglers...)
   328  	chain.extend(b)
   329  	chain.genBlocks(5, cfilterData(testPkScript))
   330  
   331  	// Create and run the scanners.
   332  	tw := NewTipWatcher(chain)
   333  	hist := NewHistorical(chain)
   334  	go func() {
   335  		tw.Run(ctx)
   336  	}()
   337  	go func() {
   338  		hist.Run(ctx)
   339  	}()
   340  
   341  	// Give it enough time for the TipWatcher to process the chain.
   342  	time.Sleep(10 * time.Millisecond)
   343  
   344  	// Attempt a search in both scanners in a consistent way. We first
   345  	// watch for the desired target in the tip watcher and use the returned
   346  	// starting watch height as the end of the historical search.
   347  	histFoundChan := make(chan Event)
   348  	tipFoundChan := make(chan Event)
   349  	swhChan := make(chan int32)
   350  
   351  	tw.Find(
   352  		ConfirmedScript(0, testPkScript),
   353  		WithFoundChan(tipFoundChan),
   354  		WithStartWatchHeightChan(swhChan),
   355  	)
   356  
   357  	endHeight := assertStartWatchHeightSignalled(t, swhChan)
   358  
   359  	// Generate a new block with the target script to simulate the tip
   360  	// changing between the call to TipWatcher.Find() and
   361  	// Historical.Find(). This could lead to multiple matches if the usage
   362  	// of the two scanners is inconsistent.
   363  	tip := chain.newFromTip(confirmManglers...)
   364  	chain.extend(tip)
   365  	chain.signalNewTip()
   366  
   367  	// The TipWatcher should trigger a match.
   368  	assertFoundChanRcvHeight(t, tipFoundChan, int32(tip.block.Header.Height))
   369  
   370  	// Run the historical scanner.
   371  	hist.Find(
   372  		ConfirmedScript(0, testPkScript),
   373  		WithFoundChan(histFoundChan),
   374  		WithEndHeight(endHeight),
   375  	)
   376  
   377  	// Generate a new tip confirming the test script.
   378  	tip = chain.newFromTip(confirmManglers...)
   379  	chain.extend(tip)
   380  	chain.signalNewTip()
   381  
   382  	// We expect to find one (and only one) signal in both chans, each
   383  	// pointing to their respective triggered event.
   384  	assertFoundChanRcvHeight(t, histFoundChan, int32(b.block.Header.Height))
   385  	assertFoundChanRcvHeight(t, tipFoundChan, int32(tip.block.Header.Height))
   386  	assertFoundChanEmpty(t, histFoundChan)
   387  	assertFoundChanEmpty(t, tipFoundChan)
   388  }