github.com/decred/dcrlnd@v0.7.6/routing/chainview/interface_test.go (about)

     1  package chainview
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"runtime"
     8  	"testing"
     9  	"time"
    10  
    11  	pb "decred.org/dcrwallet/v4/rpc/walletrpc"
    12  	"github.com/decred/dcrd/chaincfg/chainhash"
    13  	"github.com/decred/dcrd/chaincfg/v3"
    14  	"github.com/decred/dcrd/dcrec"
    15  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    16  	"github.com/decred/dcrd/dcrjson/v4"
    17  	"github.com/decred/dcrd/dcrutil/v4"
    18  	"github.com/decred/dcrd/rpcclient/v8"
    19  	"github.com/decred/dcrd/txscript/v4"
    20  	"github.com/decred/dcrd/txscript/v4/sign"
    21  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    22  	"github.com/decred/dcrd/wire"
    23  	"github.com/decred/dcrlnd/channeldb"
    24  	"github.com/decred/dcrlnd/input"
    25  	"github.com/decred/dcrlnd/internal/testutils"
    26  	"github.com/decred/dcrlnd/lntest/wait"
    27  	rpctest "github.com/decred/dcrtest/dcrdtest"
    28  	"matheusd.com/testctx"
    29  )
    30  
    31  var (
    32  	netParams = chaincfg.SimNetParams()
    33  
    34  	testPrivKey = []byte{
    35  		0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
    36  		0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
    37  		0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
    38  		0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
    39  	}
    40  
    41  	privKey   = secp256k1.PrivKeyFromBytes(testPrivKey)
    42  	pubKey    = privKey.PubKey()
    43  	addrPk, _ = stdaddr.NewAddressPubKeyEcdsaSecp256k1V0(pubKey,
    44  		netParams)
    45  	testAddr = addrPk.AddressPubKeyHash()
    46  
    47  	testScript, _ = input.PayToAddrScript(testAddr)
    48  )
    49  
    50  func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
    51  	var found bool
    52  	var tx *dcrutil.Tx
    53  	var err error
    54  	timeout := time.After(10 * time.Second)
    55  	for !found {
    56  		// Do a short wait
    57  		select {
    58  		case <-timeout:
    59  			return fmt.Errorf("timeout after 10s")
    60  		default:
    61  		}
    62  		time.Sleep(100 * time.Millisecond)
    63  
    64  		// Check for the harness' knowledge of the txid
    65  		tx, err = r.Node.GetRawTransaction(context.TODO(), txid)
    66  		if err != nil {
    67  			switch e := err.(type) {
    68  			case *dcrjson.RPCError:
    69  				if e.Code == dcrjson.ErrRPCNoTxInfo {
    70  					continue
    71  				}
    72  			default:
    73  			}
    74  			return err
    75  		}
    76  		if tx != nil && tx.MsgTx().TxHash() == *txid {
    77  			found = true
    78  		}
    79  	}
    80  	return nil
    81  }
    82  
    83  func getTestTXID(miner *rpctest.Harness) (*chainhash.Hash, error) {
    84  	script, err := input.PayToAddrScript(testAddr)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	outputs := []*wire.TxOut{
    90  		{
    91  			Value:    2e8,
    92  			PkScript: script,
    93  		},
    94  	}
    95  	return miner.SendOutputs(context.Background(), outputs, 2500)
    96  }
    97  
    98  func locateOutput(tx *wire.MsgTx, script []byte) (*wire.OutPoint, *wire.TxOut, error) {
    99  	for i, txOut := range tx.TxOut {
   100  		if bytes.Equal(txOut.PkScript, script) {
   101  			return &wire.OutPoint{
   102  				Hash:  tx.TxHash(),
   103  				Index: uint32(i),
   104  			}, txOut, nil
   105  		}
   106  	}
   107  
   108  	return nil, nil, fmt.Errorf("unable to find output")
   109  }
   110  
   111  func craftSpendTransaction(outpoint wire.OutPoint, payScript []byte) (*wire.MsgTx, error) {
   112  	spendingTx := wire.NewMsgTx()
   113  	spendingTx.AddTxIn(&wire.TxIn{
   114  		PreviousOutPoint: outpoint,
   115  	})
   116  	spendingTx.AddTxOut(&wire.TxOut{
   117  		Value:    1e8,
   118  		PkScript: payScript,
   119  	})
   120  	sigScript, err := sign.SignatureScript(spendingTx, 0, payScript,
   121  		txscript.SigHashAll, privKey.Serialize(), dcrec.STEcdsaSecp256k1, true)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	spendingTx.TxIn[0].SignatureScript = sigScript
   126  
   127  	return spendingTx, nil
   128  }
   129  
   130  func assertFilteredBlock(t *testing.T, fb *FilteredBlock, expectedHeight int64,
   131  	expectedHash *chainhash.Hash, txns []*chainhash.Hash) {
   132  
   133  	_, _, line, _ := runtime.Caller(1)
   134  
   135  	if fb.Height != expectedHeight {
   136  		t.Fatalf("line %v: block height mismatch: expected %v, got %v",
   137  			line, expectedHeight, fb.Height)
   138  	}
   139  	if !bytes.Equal(fb.Hash[:], expectedHash[:]) {
   140  		t.Fatalf("line %v: block hash mismatch: expected %v, got %v",
   141  			line, expectedHash, fb.Hash)
   142  	}
   143  	if len(fb.Transactions) != len(txns) {
   144  		t.Fatalf("line %v: expected %v transaction in filtered block, instead "+
   145  			"have %v", line, len(txns), len(fb.Transactions))
   146  	}
   147  
   148  	expectedTxids := make(map[chainhash.Hash]struct{})
   149  	for _, txn := range txns {
   150  		expectedTxids[*txn] = struct{}{}
   151  	}
   152  
   153  	for _, tx := range fb.Transactions {
   154  		txid := tx.TxHash()
   155  		delete(expectedTxids, txid)
   156  	}
   157  
   158  	if len(expectedTxids) != 0 {
   159  		t.Fatalf("line %v: missing txids: %v", line, expectedTxids)
   160  	}
   161  }
   162  
   163  func testFilterBlockNotifications(node *rpctest.Harness,
   164  	chainView FilteredChainView, chainViewInit chainViewInitFunc,
   165  	t *testing.T) {
   166  
   167  	// To start the test, we'll create to fresh outputs paying to the
   168  	// private key that we generated above.
   169  	txid1, err := getTestTXID(node)
   170  	if err != nil {
   171  		t.Fatalf("unable to get test txid1: %v", err)
   172  	}
   173  	err = waitForMempoolTx(node, txid1)
   174  	if err != nil {
   175  		t.Fatalf("unable to get test txid in mempool: %v", err)
   176  	}
   177  	txid2, err := getTestTXID(node)
   178  	if err != nil {
   179  		t.Fatalf("unable to get test txid2: %v", err)
   180  	}
   181  	err = waitForMempoolTx(node, txid2)
   182  	if err != nil {
   183  		t.Fatalf("unable to get test txid in mempool: %v", err)
   184  	}
   185  
   186  	blockChan := chainView.FilteredBlocks()
   187  
   188  	// Next we'll mine a block confirming the output generated above.
   189  	newBlockHashes, err := rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 1)
   190  	if err != nil {
   191  		t.Fatalf("unable to generate block: %v", err)
   192  	}
   193  
   194  	_, currentHeight, err := node.Node.GetBestBlock(context.TODO())
   195  	if err != nil {
   196  		t.Fatalf("unable to get current height: %v", err)
   197  	}
   198  
   199  	// We should get an update, however it shouldn't yet contain any
   200  	// filtered transaction as the filter hasn't been update.
   201  	select {
   202  	case filteredBlock := <-blockChan:
   203  		assertFilteredBlock(t, filteredBlock, currentHeight,
   204  			newBlockHashes[0], []*chainhash.Hash{})
   205  	case <-time.After(time.Second * 20):
   206  		t.Fatalf("filtered block notification didn't arrive")
   207  	}
   208  
   209  	// Now that the block has been mined, we'll fetch the two transactions
   210  	// so we can add them to the filter, and also craft transaction
   211  	// spending the outputs we created.
   212  	tx1, err := node.Node.GetRawTransaction(context.TODO(), txid1)
   213  	if err != nil {
   214  		t.Fatalf("unable to fetch transaction: %v", err)
   215  	}
   216  	tx2, err := node.Node.GetRawTransaction(context.TODO(), txid2)
   217  	if err != nil {
   218  		t.Fatalf("unable to fetch transaction: %v", err)
   219  	}
   220  
   221  	targetScript, err := input.PayToAddrScript(testAddr)
   222  	if err != nil {
   223  		t.Fatalf("unable to create target output: %v", err)
   224  	}
   225  
   226  	// Next, we'll locate the two outputs generated above that pay to use
   227  	// so we can properly add them to the filter.
   228  	outPoint1, _, err := locateOutput(tx1.MsgTx(), targetScript)
   229  	if err != nil {
   230  		t.Fatalf("unable to find output: %v", err)
   231  	}
   232  	outPoint2, _, err := locateOutput(tx2.MsgTx(), targetScript)
   233  	if err != nil {
   234  		t.Fatalf("unable to find output: %v", err)
   235  	}
   236  
   237  	_, currentHeight, err = node.Node.GetBestBlock(context.TODO())
   238  	if err != nil {
   239  		t.Fatalf("unable to get current height: %v", err)
   240  	}
   241  
   242  	// Now we'll add both outpoints to the current filter.
   243  	filter := []channeldb.EdgePoint{
   244  		{FundingPkScript: targetScript, OutPoint: *outPoint1},
   245  		{FundingPkScript: targetScript, OutPoint: *outPoint2},
   246  	}
   247  	err = chainView.UpdateFilter(filter, currentHeight)
   248  	if err != nil {
   249  		t.Fatalf("unable to update filter: %v", err)
   250  	}
   251  
   252  	// With the filter updated, we'll now create two transaction spending
   253  	// the outputs we created.
   254  	spendingTx1, err := craftSpendTransaction(*outPoint1, targetScript)
   255  	if err != nil {
   256  		t.Fatalf("unable to create spending tx: %v", err)
   257  	}
   258  	spendingTx2, err := craftSpendTransaction(*outPoint2, targetScript)
   259  	if err != nil {
   260  		t.Fatalf("unable to create spending tx: %v", err)
   261  	}
   262  
   263  	// Now we'll broadcast the first spending transaction and also mine a
   264  	// block which should include it.
   265  	spendTxid1, err := node.Node.SendRawTransaction(context.TODO(), spendingTx1, true)
   266  	if err != nil {
   267  		t.Fatalf("unable to broadcast transaction: %v", err)
   268  	}
   269  	err = waitForMempoolTx(node, spendTxid1)
   270  	if err != nil {
   271  		t.Fatalf("unable to get spending txid in mempool: %v", err)
   272  	}
   273  	newBlockHashes, err = rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 1)
   274  	if err != nil {
   275  		t.Fatalf("unable to generate block: %v", err)
   276  	}
   277  
   278  	// We should receive a notification over the channel. The notification
   279  	// should correspond to the current block height and have that single
   280  	// filtered transaction.
   281  	select {
   282  	case filteredBlock := <-blockChan:
   283  		assertFilteredBlock(t, filteredBlock, currentHeight+1,
   284  			newBlockHashes[0], []*chainhash.Hash{spendTxid1})
   285  	case <-time.After(time.Second * 20):
   286  		t.Fatalf("filtered block notification didn't arrive")
   287  	}
   288  
   289  	// Next, mine the second transaction which spends the second output.
   290  	// This should also generate a notification.
   291  	spendTxid2, err := node.Node.SendRawTransaction(context.TODO(), spendingTx2, true)
   292  	if err != nil {
   293  		t.Fatalf("unable to broadcast transaction: %v", err)
   294  	}
   295  	err = waitForMempoolTx(node, spendTxid2)
   296  	if err != nil {
   297  		t.Fatalf("unable to get spending txid in mempool: %v", err)
   298  	}
   299  	newBlockHashes, err = rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 1)
   300  	if err != nil {
   301  		t.Fatalf("unable to generate block: %v", err)
   302  	}
   303  
   304  	select {
   305  	case filteredBlock := <-blockChan:
   306  		assertFilteredBlock(t, filteredBlock, currentHeight+2,
   307  			newBlockHashes[0], []*chainhash.Hash{spendTxid2})
   308  	case <-time.After(time.Second * 20):
   309  		t.Fatalf("filtered block notification didn't arrive")
   310  	}
   311  }
   312  
   313  func testUpdateFilterBackTrack(node *rpctest.Harness,
   314  	chainView FilteredChainView, chainViewInit chainViewInitFunc,
   315  	t *testing.T) {
   316  
   317  	// To start, we'll create a fresh output paying to the height generated
   318  	// above.
   319  	txid, err := getTestTXID(node)
   320  	if err != nil {
   321  		t.Fatalf("unable to get test txid")
   322  	}
   323  	err = waitForMempoolTx(node, txid)
   324  	if err != nil {
   325  		t.Fatalf("unable to get test txid in mempool: %v", err)
   326  	}
   327  
   328  	// Next we'll mine a block confirming the output generated above.
   329  	initBlockHashes, err := rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 1)
   330  	if err != nil {
   331  		t.Fatalf("unable to generate block: %v", err)
   332  	}
   333  
   334  	blockChan := chainView.FilteredBlocks()
   335  
   336  	_, currentHeight, err := node.Node.GetBestBlock(context.TODO())
   337  	if err != nil {
   338  		t.Fatalf("unable to get current height: %v", err)
   339  	}
   340  
   341  	// Consume the notification sent which contains an empty filtered
   342  	// block.
   343  	select {
   344  	case filteredBlock := <-blockChan:
   345  		assertFilteredBlock(t, filteredBlock, currentHeight,
   346  			initBlockHashes[0], []*chainhash.Hash{})
   347  	case <-time.After(time.Second * 20):
   348  		t.Fatalf("filtered block notification didn't arrive")
   349  	}
   350  
   351  	// Next, create a transaction which spends the output created above,
   352  	// mining the spend into a block.
   353  	tx, err := node.Node.GetRawTransaction(context.TODO(), txid)
   354  	if err != nil {
   355  		t.Fatalf("unable to fetch transaction: %v", err)
   356  	}
   357  	outPoint, _, err := locateOutput(tx.MsgTx(), testScript)
   358  	if err != nil {
   359  		t.Fatalf("unable to find output: %v", err)
   360  	}
   361  	spendingTx, err := craftSpendTransaction(*outPoint, testScript)
   362  	if err != nil {
   363  		t.Fatalf("unable to create spending tx: %v", err)
   364  	}
   365  	spendTxid, err := node.Node.SendRawTransaction(context.TODO(), spendingTx, true)
   366  	if err != nil {
   367  		t.Fatalf("unable to broadcast transaction: %v", err)
   368  	}
   369  	err = waitForMempoolTx(node, spendTxid)
   370  	if err != nil {
   371  		t.Fatalf("unable to get spending txid in mempool: %v", err)
   372  	}
   373  	newBlockHashes, err := rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 1)
   374  	if err != nil {
   375  		t.Fatalf("unable to generate block: %v", err)
   376  	}
   377  
   378  	// We should have received another empty filtered block notification.
   379  	select {
   380  	case filteredBlock := <-blockChan:
   381  		assertFilteredBlock(t, filteredBlock, currentHeight+1,
   382  			newBlockHashes[0], []*chainhash.Hash{})
   383  	case <-time.After(time.Second * 20):
   384  		t.Fatalf("filtered block notification didn't arrive")
   385  	}
   386  
   387  	// After the block has been mined+notified we'll update the filter with
   388  	// a _prior_ height so a "rewind" occurs.
   389  	filter := []channeldb.EdgePoint{
   390  		{FundingPkScript: testScript, OutPoint: *outPoint},
   391  	}
   392  	err = chainView.UpdateFilter(filter, currentHeight)
   393  	if err != nil {
   394  		t.Fatalf("unable to update filter: %v", err)
   395  	}
   396  
   397  	// We should now receive a fresh filtered block notification that
   398  	// includes the transaction spend we included above.
   399  	select {
   400  	case filteredBlock := <-blockChan:
   401  		assertFilteredBlock(t, filteredBlock, currentHeight+1,
   402  			newBlockHashes[0], []*chainhash.Hash{spendTxid})
   403  	case <-time.After(time.Second * 20):
   404  		t.Fatalf("filtered block notification didn't arrive")
   405  	}
   406  }
   407  
   408  func testFilterSingleBlock(node *rpctest.Harness, chainView FilteredChainView,
   409  	chainViewInit chainViewInitFunc, t *testing.T) {
   410  
   411  	// In this test, we'll test the manual filtration of blocks, which can
   412  	// be used by clients to manually rescan their sub-set of the UTXO set.
   413  
   414  	// First, we'll create a block that includes two outputs that we're
   415  	// able to spend with the private key generated above.
   416  	txid1, err := getTestTXID(node)
   417  	if err != nil {
   418  		t.Fatalf("unable to get test txid")
   419  	}
   420  	err = waitForMempoolTx(node, txid1)
   421  	if err != nil {
   422  		t.Fatalf("unable to get test txid in mempool: %v", err)
   423  	}
   424  	txid2, err := getTestTXID(node)
   425  	if err != nil {
   426  		t.Fatalf("unable to get test txid")
   427  	}
   428  	err = waitForMempoolTx(node, txid2)
   429  	if err != nil {
   430  		t.Fatalf("unable to get test txid in mempool: %v", err)
   431  	}
   432  
   433  	blockChan := chainView.FilteredBlocks()
   434  
   435  	// Next we'll mine a block confirming the output generated above.
   436  	newBlockHashes, err := rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 1)
   437  	if err != nil {
   438  		t.Fatalf("unable to generate block: %v", err)
   439  	}
   440  
   441  	_, currentHeight, err := node.Node.GetBestBlock(context.TODO())
   442  	if err != nil {
   443  		t.Fatalf("unable to get current height: %v", err)
   444  	}
   445  
   446  	// We should get an update, however it shouldn't yet contain any
   447  	// filtered transaction as the filter hasn't been updated.
   448  	select {
   449  	case filteredBlock := <-blockChan:
   450  		assertFilteredBlock(t, filteredBlock, currentHeight,
   451  			newBlockHashes[0], nil)
   452  	case <-time.After(time.Second * 20):
   453  		t.Fatalf("filtered block notification didn't arrive")
   454  	}
   455  
   456  	tx1, err := node.Node.GetRawTransaction(context.TODO(), txid1)
   457  	if err != nil {
   458  		t.Fatalf("unable to fetch transaction: %v", err)
   459  	}
   460  	tx2, err := node.Node.GetRawTransaction(context.TODO(), txid2)
   461  	if err != nil {
   462  		t.Fatalf("unable to fetch transaction: %v", err)
   463  	}
   464  
   465  	// Next, we'll create a block that includes two transactions, each
   466  	// which spend one of the outputs created.
   467  	outPoint1, _, err := locateOutput(tx1.MsgTx(), testScript)
   468  	if err != nil {
   469  		t.Fatalf("unable to find output: %v", err)
   470  	}
   471  	outPoint2, _, err := locateOutput(tx2.MsgTx(), testScript)
   472  	if err != nil {
   473  		t.Fatalf("unable to find output: %v", err)
   474  	}
   475  	spendingTx1, err := craftSpendTransaction(*outPoint1, testScript)
   476  	if err != nil {
   477  		t.Fatalf("unable to create spending tx1: %v", err)
   478  	}
   479  	spendingTx2, err := craftSpendTransaction(*outPoint2, testScript)
   480  	if err != nil {
   481  		t.Fatalf("unable to create spending tx2: %v", err)
   482  	}
   483  	_, err = node.Node.SendRawTransaction(context.TODO(), spendingTx1, true)
   484  	if err != nil {
   485  		t.Fatalf("unable to send spending tx1: %v", err)
   486  	}
   487  	_, err = node.Node.SendRawTransaction(context.TODO(), spendingTx2, true)
   488  	if err != nil {
   489  		t.Fatalf("unable to send spending tx2: %v", err)
   490  	}
   491  	blockHashes, err := rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 1)
   492  	if err != nil {
   493  		t.Fatalf("unable to generate block: %v", err)
   494  	}
   495  
   496  	select {
   497  	case filteredBlock := <-blockChan:
   498  		assertFilteredBlock(t, filteredBlock, currentHeight+1,
   499  			blockHashes[0], nil)
   500  	case <-time.After(time.Second * 20):
   501  		t.Fatalf("filtered block notification didn't arrive")
   502  	}
   503  
   504  	_, currentHeight, err = node.Node.GetBestBlock(context.TODO())
   505  	if err != nil {
   506  		t.Fatalf("unable to get current height: %v", err)
   507  	}
   508  
   509  	// Now we'll manually trigger filtering the block generated above.
   510  	// First, we'll add the two outpoints to our filter.
   511  	filter := []channeldb.EdgePoint{
   512  		{FundingPkScript: testScript, OutPoint: *outPoint1},
   513  		{FundingPkScript: testScript, OutPoint: *outPoint2},
   514  	}
   515  	err = chainView.UpdateFilter(filter, currentHeight)
   516  	if err != nil {
   517  		t.Fatalf("unable to update filter: %v", err)
   518  	}
   519  
   520  	// We set the filter with the current height, so we shouldn't get any
   521  	// notifications.
   522  	select {
   523  	case <-blockChan:
   524  		t.Fatalf("got filter notification, but shouldn't have")
   525  	default:
   526  	}
   527  
   528  	// Now we'll manually rescan that past block. This should include two
   529  	// filtered transactions, the spending transactions we created above.
   530  	filteredBlock, err := chainView.FilterBlock(blockHashes[0])
   531  	if err != nil {
   532  		t.Fatalf("unable to filter block: %v", err)
   533  	}
   534  	txn1, txn2 := spendingTx1.TxHash(), spendingTx2.TxHash()
   535  	expectedTxns := []*chainhash.Hash{&txn1, &txn2}
   536  	assertFilteredBlock(t, filteredBlock, currentHeight, blockHashes[0],
   537  		expectedTxns)
   538  }
   539  
   540  // testFilterBlockDisconnected triggers a reorg all the way back to genesis,
   541  // and a small 5 block reorg, ensuring the chainView notifies about
   542  // disconnected and connected blocks in the order we expect.
   543  func testFilterBlockDisconnected(node *rpctest.Harness,
   544  	chainView FilteredChainView, chainViewInit chainViewInitFunc,
   545  	t *testing.T) {
   546  
   547  	ctxb := context.Background()
   548  
   549  	// NOTE(decred): the original upstream test that reorgs all the way to
   550  	// genesis has been removed here in favor of only testing the 10-way
   551  	// reorg due to SPV not (easily) exposing a reorg all the way to
   552  	// genesis.
   553  
   554  	// Create a node that has a shorter chain than the main chain, so we
   555  	// can trigger a reorg.
   556  	reorgNode, err := testutils.NewSetupRPCTest(
   557  		testctx.New(t), 5, netParams, nil, []string{"--txindex"}, false, 0,
   558  	)
   559  	if err != nil {
   560  		t.Fatalf("unable to create mining node: %v", err)
   561  	}
   562  	defer reorgNode.TearDown()
   563  
   564  	// Connect the node with the short chain to the main node, and wait
   565  	// for their chains to synchronize.
   566  	if err := rpctest.ConnectNode(ctxb, reorgNode, node); err != nil {
   567  		t.Fatalf("unable to connect harnesses: %v", err)
   568  	}
   569  	nodeSlice := []*rpctest.Harness{node, reorgNode}
   570  	if err := rpctest.JoinNodes(ctxb, nodeSlice, rpctest.Blocks); err != nil {
   571  		t.Fatalf("unable to join node on blocks: %v", err)
   572  	}
   573  
   574  	// Init a chain view that has this node as its block source.
   575  	cleanUpFunc, reorgView, err := chainViewInit(t, reorgNode)
   576  	if err != nil {
   577  		t.Fatalf("unable to create chain view: %v", err)
   578  	}
   579  	defer func() {
   580  		if cleanUpFunc != nil {
   581  			cleanUpFunc()
   582  		}
   583  	}()
   584  
   585  	if err = reorgView.Start(); err != nil {
   586  		t.Fatalf("unable to start dcrd chain view: %v", err)
   587  	}
   588  	defer reorgView.Stop()
   589  
   590  	newBlocks := reorgView.FilteredBlocks()
   591  	disconnectedBlocks := reorgView.DisconnectedBlocks()
   592  
   593  	_, newHeight, err := reorgNode.Node.GetBestBlock(context.TODO())
   594  	if err != nil {
   595  		t.Fatalf("unable to get current height: %v", err)
   596  	}
   597  
   598  	// Now we trigger a small reorg, by disconnecting the nodes, mining
   599  	// a few blocks on each, then connecting them again.
   600  	peers, err := reorgNode.Node.GetPeerInfo(context.TODO())
   601  	if err != nil {
   602  		t.Fatalf("unable to get peer info: %v", err)
   603  	}
   604  	numPeers := len(peers)
   605  
   606  	// TODO(decred): This is hacky.  Ideally there should be a way to get the
   607  	// peer address from the passed in node directly rather than assuming it
   608  	// is the first connected peer which is brittle if the tests change.
   609  	//
   610  	// Disconnect the nodes.
   611  	if numPeers < 1 {
   612  		t.Fatalf("no connected peer")
   613  	}
   614  	err = reorgNode.Node.AddNode(context.TODO(), peers[0].Addr, rpcclient.ANRemove)
   615  	if err != nil {
   616  		t.Fatalf("unable to disconnect mining nodes: %v", err)
   617  	}
   618  
   619  	// Wait for disconnection
   620  	for {
   621  		peers, err = reorgNode.Node.GetPeerInfo(context.TODO())
   622  		if err != nil {
   623  			t.Fatalf("unable to get peer info: %v", err)
   624  		}
   625  		if len(peers) < numPeers {
   626  			break
   627  		}
   628  		time.Sleep(100 * time.Millisecond)
   629  	}
   630  
   631  	// Mine 10 blocks on the main chain, 5 on the chain that will be
   632  	// reorged out,
   633  	if _, err := rpctest.AdjustedSimnetMiner(context.Background(), node.Node, 10); err != nil {
   634  		t.Fatal(err)
   635  	}
   636  	if _, err := rpctest.AdjustedSimnetMiner(context.Background(), reorgNode.Node, 5); err != nil {
   637  		t.Fatal(err)
   638  	}
   639  
   640  	// 5 new blocks should get notified.
   641  	for i := int64(0); i < 5; i++ {
   642  		select {
   643  		case block := <-newBlocks:
   644  			expectedHeight := newHeight + i + 1
   645  			if block.Height != expectedHeight {
   646  				t.Fatalf("expected to receive connected "+
   647  					"block at height %d, instead got at %d",
   648  					expectedHeight, block.Height)
   649  			}
   650  		case <-disconnectedBlocks:
   651  			t.Fatalf("did not expect to get stale block "+
   652  				"in iteration %d", i)
   653  		case <-time.After(10 * time.Second):
   654  			t.Fatalf("did not get connected block")
   655  		}
   656  	}
   657  
   658  	_, oldHeight, err := reorgNode.Node.GetBestBlock(context.TODO())
   659  	if err != nil {
   660  		t.Fatalf("unable to get current height: %v", err)
   661  	}
   662  
   663  	// Now connect the two nodes, and wait for their chains to sync up.
   664  	if err := rpctest.ConnectNode(ctxb, reorgNode, node); err != nil {
   665  		t.Fatalf("unable to connect harnesses: %v", err)
   666  	}
   667  	if err := rpctest.JoinNodes(ctxb, nodeSlice, rpctest.Blocks); err != nil {
   668  		t.Fatalf("unable to join node on blocks: %v", err)
   669  	}
   670  
   671  	_, _, err = reorgNode.Node.GetBestBlock(context.TODO())
   672  	if err != nil {
   673  		t.Fatalf("unable to get current height: %v", err)
   674  	}
   675  
   676  	// We should get 5 disconnected, 10 connected blocks.
   677  	for i := int64(0); i < 15; i++ {
   678  		select {
   679  		case block := <-newBlocks:
   680  			if i < 5 {
   681  				t.Fatalf("did not expect to get new block "+
   682  					"in iteration %d", i)
   683  			}
   684  			// The expected height for the connected block will be
   685  			// oldHeight - 5 (the 5 disconnected blocks) + (i-5)
   686  			// (subtract 5 since the 5 first iterations consumed
   687  			// disconnected blocks) + 1
   688  			expectedHeight := oldHeight - 9 + i
   689  			if block.Height != expectedHeight {
   690  				t.Fatalf("expected to receive connected "+
   691  					"block at height %d, instead got at %d",
   692  					expectedHeight, block.Height)
   693  			}
   694  		case block := <-disconnectedBlocks:
   695  			if i >= 5 {
   696  				t.Fatalf("did not expect to get stale block "+
   697  					"in iteration %d", i)
   698  			}
   699  			expectedHeight := oldHeight - i
   700  			if block.Height != expectedHeight {
   701  				t.Fatalf("expected to receive disconnected "+
   702  					"block at height %d, instead got at %d",
   703  					expectedHeight, block.Height)
   704  			}
   705  		case <-time.After(10 * time.Second):
   706  			t.Fatalf("did not get disconnected block")
   707  		}
   708  	}
   709  
   710  	// Time for db access to finish between testcases.
   711  	time.Sleep(time.Millisecond * 500)
   712  }
   713  
   714  type chainViewInitFunc func(t testutils.TB, miner *rpctest.Harness) (func(), FilteredChainView, error)
   715  
   716  type testCase struct {
   717  	name string
   718  	test func(*rpctest.Harness, FilteredChainView, chainViewInitFunc,
   719  		*testing.T)
   720  }
   721  
   722  var chainViewTests = []testCase{
   723  	{
   724  		name: "filtered block ntfns",
   725  		test: testFilterBlockNotifications,
   726  	},
   727  	{
   728  		name: "update filter back track",
   729  		test: testUpdateFilterBackTrack,
   730  	},
   731  	{
   732  		name: "filter single block",
   733  		test: testFilterSingleBlock,
   734  	},
   735  	{
   736  		name: "filter block disconnected",
   737  		test: testFilterBlockDisconnected,
   738  	},
   739  }
   740  
   741  var interfaceImpls = []struct {
   742  	name          string
   743  	chainViewInit chainViewInitFunc
   744  }{
   745  	{
   746  		name: "dcrd_websockets",
   747  		chainViewInit: func(t testutils.TB, miner *rpctest.Harness) (func(), FilteredChainView, error) {
   748  			chainView, err := NewDcrdFilteredChainView(miner.RPCConfig())
   749  			if err != nil {
   750  				return nil, nil, err
   751  			}
   752  
   753  			return nil, chainView, err
   754  		},
   755  	},
   756  	{
   757  		name: "dcrw_embedded_dcrd",
   758  		chainViewInit: func(t testutils.TB, miner *rpctest.Harness) (func(), FilteredChainView, error) {
   759  			config := miner.RPCConfig()
   760  			w, teardown := testutils.NewRPCSyncingTestWallet(t, &config)
   761  			chainView, err := NewDcrwalletFilteredChainView(w, nil)
   762  			if err != nil {
   763  				return nil, nil, err
   764  			}
   765  
   766  			return teardown, chainView, err
   767  		},
   768  	},
   769  	{
   770  		name: "dcrw_embedded_spv",
   771  		chainViewInit: func(t testutils.TB, miner *rpctest.Harness) (func(), FilteredChainView, error) {
   772  			w, teardown := testutils.NewSPVSyncingTestWallet(t, miner.P2PAddress())
   773  			chainView, err := NewDcrwalletFilteredChainView(w, nil)
   774  			if err != nil {
   775  				return nil, nil, err
   776  			}
   777  
   778  			// Wait until the wallet has fully synced.
   779  			_, bestHeight, err := miner.Node.GetBestBlock(context.Background())
   780  			if err != nil {
   781  				return nil, nil, err
   782  			}
   783  			err = wait.NoError(func() error {
   784  				_, height := w.MainChainTip(context.Background())
   785  				if int64(height) != bestHeight {
   786  					return fmt.Errorf("wallet height %d not miner height %d", height, bestHeight)
   787  				}
   788  				return nil
   789  			}, 30*time.Second)
   790  
   791  			return teardown, chainView, err
   792  		},
   793  	},
   794  	{
   795  		name: "dcrw_remote_dcrd",
   796  		chainViewInit: func(t testutils.TB, miner *rpctest.Harness) (func(), FilteredChainView, error) {
   797  			config := miner.RPCConfig()
   798  			conn, teardown := testutils.NewRPCSyncingTestRemoteDcrwallet(t, &config)
   799  			chainView, err := NewRemoteWalletFilteredChainView(conn, nil)
   800  			if err != nil {
   801  				return nil, nil, err
   802  			}
   803  
   804  			return teardown, chainView, err
   805  		},
   806  	},
   807  	{
   808  		name: "dcrw_remote_spv",
   809  		chainViewInit: func(t testutils.TB, miner *rpctest.Harness) (func(), FilteredChainView, error) {
   810  			conn, teardown := testutils.NewSPVSyncingTestRemoteDcrwallet(t, miner.P2PAddress())
   811  			chainView, err := NewRemoteWalletFilteredChainView(conn, nil)
   812  			if err != nil {
   813  				return nil, nil, err
   814  			}
   815  
   816  			// Wait until the wallet has fully synced.
   817  			_, bestHeight, err := miner.Node.GetBestBlock(context.Background())
   818  			if err != nil {
   819  				return nil, nil, err
   820  			}
   821  			wallet := pb.NewWalletServiceClient(conn)
   822  			err = wait.NoError(func() error {
   823  				res, err := wallet.BestBlock(context.Background(), &pb.BestBlockRequest{})
   824  				if err != nil {
   825  					return err
   826  				}
   827  				if int64(res.Height) != bestHeight {
   828  					return fmt.Errorf("wallet height %d not miner height %d", res.Height, bestHeight)
   829  				}
   830  				return nil
   831  			}, 30*time.Second)
   832  
   833  			return teardown, chainView, err
   834  		},
   835  	},
   836  }
   837  
   838  func TestFilteredChainView(t *testing.T) {
   839  	for _, chainViewImpl := range interfaceImpls {
   840  		t.Run(chainViewImpl.name, func(t *testing.T) {
   841  			// Initialize the harness around a dcrd node which will serve as our
   842  			// dedicated miner to generate blocks, cause re-orgs, etc. We'll set up
   843  			// this node with a chain length of 25, so we have plenty of DCR to
   844  			// play around with.
   845  			miner, err := testutils.NewSetupRPCTest(
   846  				testctx.New(t), 5, netParams, nil, []string{"--txindex"}, true, 25,
   847  			)
   848  			if err != nil {
   849  				t.Fatalf("unable to create mining node: %v", err)
   850  			}
   851  			defer miner.TearDown()
   852  
   853  			cleanUpFunc, chainView, err := chainViewImpl.chainViewInit(t, miner)
   854  			if err != nil {
   855  				t.Fatalf("unable to make chain view: %v", err)
   856  			}
   857  
   858  			if err := chainView.Start(); err != nil {
   859  				t.Fatalf("unable to start chain view: %v", err)
   860  			}
   861  			for _, chainViewTest := range chainViewTests {
   862  				testName := fmt.Sprintf("%v", chainViewTest.name)
   863  				success := t.Run(testName, func(t *testing.T) {
   864  					chainViewTest.test(miner, chainView,
   865  						chainViewImpl.chainViewInit, t)
   866  				})
   867  
   868  				if !success {
   869  					break
   870  				}
   871  			}
   872  
   873  			if err := chainView.Stop(); err != nil {
   874  				t.Fatalf("unable to stop chain view: %v", err)
   875  			}
   876  
   877  			if cleanUpFunc != nil {
   878  				cleanUpFunc()
   879  			}
   880  		})
   881  	}
   882  }