github.com/decred/dcrlnd@v0.7.6/chainntnfs/dcrdnotify/dcrd_test.go (about)

     1  //go:build dev
     2  // +build dev
     3  
     4  package dcrdnotify
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"io/ioutil"
    10  	"testing"
    11  
    12  	"github.com/decred/dcrd/chaincfg/chainhash"
    13  	"github.com/decred/dcrd/chaincfg/v3"
    14  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    15  	"github.com/decred/dcrd/txscript/v4/stdscript"
    16  	"github.com/decred/dcrd/wire"
    17  	"github.com/decred/dcrlnd/chainntnfs"
    18  	"github.com/decred/dcrlnd/chainscan"
    19  	"github.com/decred/dcrlnd/channeldb"
    20  	"github.com/decred/dcrlnd/internal/testutils"
    21  	rpctest "github.com/decred/dcrtest/dcrdtest"
    22  	"github.com/stretchr/testify/require"
    23  	"matheusd.com/testctx"
    24  )
    25  
    26  var (
    27  	testScript = []byte{
    28  		// OP_HASH160
    29  		0xA9,
    30  		// OP_DATA_20
    31  		0x14,
    32  		// <20-byte hash>
    33  		0xec, 0x6f, 0x7a, 0x5a, 0xa8, 0xf2, 0xb1, 0x0c, 0xa5, 0x15,
    34  		0x04, 0x52, 0x3a, 0x60, 0xd4, 0x03, 0x06, 0xf6, 0x96, 0xcd,
    35  		// OP_EQUAL
    36  		0x87,
    37  	}
    38  
    39  	netParams = chaincfg.SimNetParams()
    40  )
    41  
    42  func initHintCache(t *testing.T) *chainntnfs.HeightHintCache {
    43  	t.Helper()
    44  
    45  	tempDir, err := ioutil.TempDir("", "kek")
    46  	if err != nil {
    47  		t.Fatalf("unable to create temp dir: %v", err)
    48  	}
    49  	db, err := channeldb.Open(tempDir)
    50  	if err != nil {
    51  		t.Fatalf("unable to create db: %v", err)
    52  	}
    53  	testCfg := chainntnfs.CacheConfig{
    54  		QueryDisable: false,
    55  	}
    56  	hintCache, err := chainntnfs.NewHeightHintCache(testCfg, db.Backend)
    57  	if err != nil {
    58  		t.Fatalf("unable to create hint cache: %v", err)
    59  	}
    60  
    61  	return hintCache
    62  }
    63  
    64  // setUpNotifier is a helper function to start a new notifier backed by a dcrd
    65  // driver.
    66  func setUpNotifier(t *testing.T, h *rpctest.Harness) *DcrdNotifier {
    67  	hintCache := initHintCache(t)
    68  
    69  	rpcConfig := h.RPCConfig()
    70  	notifier, err := New(&rpcConfig, netParams, hintCache, hintCache, nil)
    71  	if err != nil {
    72  		t.Fatalf("unable to create notifier: %v", err)
    73  	}
    74  	if err := notifier.Start(); err != nil {
    75  		t.Fatalf("unable to start notifier: %v", err)
    76  	}
    77  
    78  	return notifier
    79  }
    80  
    81  // TestHistoricalConfDetailsTxIndex ensures that we correctly retrieve
    82  // historical confirmation details using the backend node's txindex.
    83  func TestHistoricalConfDetailsTxIndex(t *testing.T) {
    84  	t.Parallel()
    85  
    86  	harness, err := testutils.NewSetupRPCTest(
    87  		testctx.New(t), 5, netParams, nil, []string{"--txindex"}, true, 25,
    88  	)
    89  	require.NoError(t, err)
    90  	defer harness.TearDown()
    91  
    92  	notifier := setUpNotifier(t, harness)
    93  	defer notifier.Stop()
    94  
    95  	// A transaction unknown to the node should not be found within the
    96  	// txindex even if it is enabled, so we should not proceed with any
    97  	// fallback methods.
    98  	var unknownHash chainhash.Hash
    99  	copy(unknownHash[:], bytes.Repeat([]byte{0x10}, 32))
   100  	unknownConfReq, err := chainntnfs.NewConfRequest(&unknownHash, testScript)
   101  	if err != nil {
   102  		t.Fatalf("unable to create conf request: %v", err)
   103  	}
   104  	_, txStatus, err := notifier.historicalConfDetails(unknownConfReq, 0, 0)
   105  	if err != nil {
   106  		t.Fatalf("unable to retrieve historical conf details: %v", err)
   107  	}
   108  
   109  	switch txStatus {
   110  	case chainntnfs.TxNotFoundIndex:
   111  	case chainntnfs.TxNotFoundManually:
   112  		t.Fatal("should not have proceeded with fallback method, but did")
   113  	default:
   114  		t.Fatal("should not have found non-existent transaction, but did")
   115  	}
   116  
   117  	// Now, we'll create a test transaction and attempt to retrieve its
   118  	// confirmation details.
   119  	txid, pkScript, err := chainntnfs.GetTestTxidAndScript(harness)
   120  	if err != nil {
   121  		t.Fatalf("unable to create tx: %v", err)
   122  	}
   123  	if err := chainntnfs.WaitForMempoolTx(harness, txid); err != nil {
   124  		t.Fatalf("unable to find tx in the mempool: %v", err)
   125  	}
   126  	confReq, err := chainntnfs.NewConfRequest(txid, pkScript)
   127  	if err != nil {
   128  		t.Fatalf("unable to create conf request: %v", err)
   129  	}
   130  
   131  	// The transaction should be found in the mempool at this point.
   132  	_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
   133  	if err != nil {
   134  		t.Fatalf("unable to retrieve historical conf details: %v", err)
   135  	}
   136  
   137  	// Since it has yet to be included in a block, it should have been found
   138  	// within the mempool.
   139  	switch txStatus {
   140  	case chainntnfs.TxFoundMempool:
   141  	default:
   142  		t.Fatalf("should have found the transaction within the "+
   143  			"mempool, but did not: %v", txStatus)
   144  	}
   145  
   146  	// We'll now confirm this transaction and re-attempt to retrieve its
   147  	// confirmation details.
   148  	if _, err := rpctest.AdjustedSimnetMiner(context.Background(), harness.Node, 1); err != nil {
   149  		t.Fatalf("unable to generate block: %v", err)
   150  	}
   151  
   152  	_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
   153  	if err != nil {
   154  		t.Fatalf("unable to retrieve historical conf details: %v", err)
   155  	}
   156  
   157  	// Since the backend node's txindex is enabled and the transaction has
   158  	// confirmed, we should be able to retrieve it using the txindex.
   159  	switch txStatus {
   160  	case chainntnfs.TxFoundIndex:
   161  	default:
   162  		t.Fatal("should have found the transaction within the " +
   163  			"txindex, but did not")
   164  	}
   165  }
   166  
   167  // TestHistoricalConfDetailsNoTxIndex ensures that we correctly retrieve
   168  // historical confirmation details using the set of fallback methods when the
   169  // backend node's txindex is disabled.
   170  //
   171  // TODO(decred) rpctest currently always creates nodes with --txindex and
   172  // --addrindex, so this test can't be executed at this time. It can manually
   173  // verified by locally modifying a copy of rpctest and adding a replace
   174  // directive in the top level go.mod file. Commenting this test for the moment.
   175  /*
   176  func TestHistoricalConfDetailsNoTxIndex(t *testing.T) {
   177  	t.Parallel()
   178  
   179  	harness, err := testutils.NewSetupRPCTest(
   180  		testctx.New(t), 5, netParams, nil, []string{"--txindex"}, true, 25,
   181  	)
   182  	require.NoError(t, err)
   183  	defer harness.TearDown()
   184  
   185  	notifier := setUpNotifier(t, harness)
   186  	defer notifier.Stop()
   187  
   188  	// Since the node has its txindex disabled, we fall back to scanning the
   189  	// chain manually. A transaction unknown to the network should not be
   190  	// found.
   191  	var unknownHash chainhash.Hash
   192  	copy(unknownHash[:], bytes.Repeat([]byte{0x10}, 32))
   193  	unknownConfReq, err := chainntnfs.NewConfRequest(&unknownHash, testScript)
   194  	if err != nil {
   195  		t.Fatalf("unable to create conf request: %v", err)
   196  	}
   197  	_, txStatus, err := notifier.historicalConfDetails(unknownConfReq, 0, 0)
   198  	if err != nil {
   199  		t.Fatalf("unable to retrieve historical conf details: %v", err)
   200  	}
   201  
   202  	switch txStatus {
   203  	case chainntnfs.TxNotFoundManually:
   204  	case chainntnfs.TxNotFoundIndex:
   205  		t.Fatal("should have proceeded with fallback method, but did not")
   206  	default:
   207  		t.Fatal("should not have found non-existent transaction, but did")
   208  	}
   209  
   210  	// Now, we'll create a test transaction and attempt to retrieve its
   211  	// confirmation details. We'll note its broadcast height to use as the
   212  	// height hint when manually scanning the chain.
   213  	_, currentHeight, err := harness.Node.GetBestBlock()
   214  	if err != nil {
   215  		t.Fatalf("unable to retrieve current height: %v", err)
   216  	}
   217  
   218  	txid, pkScript, err := chainntnfs.GetTestTxidAndScript(harness)
   219  	if err != nil {
   220  		t.Fatalf("unable to create tx: %v", err)
   221  	}
   222  	if err := chainntnfs.WaitForMempoolTx(harness, txid); err != nil {
   223  		t.Fatalf("unable to find tx in the mempool: %v", err)
   224  	}
   225  	confReq, err := chainntnfs.NewConfRequest(txid, pkScript)
   226  	if err != nil {
   227  		t.Fatalf("unable to create conf request: %v", err)
   228  	}
   229  
   230  	_, txStatus, err = notifier.historicalConfDetails(confReq, 0, 0)
   231  	if err != nil {
   232  		t.Fatalf("unable to retrieve historical conf details: %v", err)
   233  	}
   234  
   235  	// Since it has yet to be included in a block, it should have been found
   236  	// within the mempool.
   237  	if txStatus != chainntnfs.TxFoundMempool {
   238  		t.Fatal("should have found the transaction within the " +
   239  			"mempool, but did not")
   240  	}
   241  
   242  	// We'll now confirm this transaction and re-attempt to retrieve its
   243  	// confirmation details.
   244  	if _, err := harness.Node.Generate(1); err != nil {
   245  		t.Fatalf("unable to generate block: %v", err)
   246  	}
   247  
   248  	_, txStatus, err = notifier.historicalConfDetails(
   249  		confReq, uint32(currentHeight), uint32(currentHeight)+1,
   250  	)
   251  	if err != nil {
   252  		t.Fatalf("unable to retrieve historical conf details: %v", err)
   253  	}
   254  
   255  	// Since the backend node's txindex is disabled and the transaction has
   256  	// confirmed, we should be able to find it by falling back to scanning
   257  	// the chain manually.
   258  	if txStatus != chainntnfs.TxFoundManually {
   259  		t.Fatal("should have found the transaction by manually " +
   260  			"scanning the chain, but did not")
   261  	}
   262  }
   263  */
   264  
   265  // TestInneficientRescan tests whether the inneficient per block rescan works
   266  // as required to detect spent outpoints and scripts.
   267  func TestInneficientRescan(t *testing.T) {
   268  	t.Parallel()
   269  
   270  	harness, err := testutils.NewSetupRPCTest(
   271  		testctx.New(t), 5, netParams, nil, []string{"--txindex"}, true, 25,
   272  	)
   273  	require.NoError(t, err)
   274  	defer harness.TearDown()
   275  
   276  	notifier := setUpNotifier(t, harness)
   277  	defer notifier.Stop()
   278  
   279  	// Create an output and subsequently spend it.
   280  	outpoint, txout, privKey := chainntnfs.CreateSpendableOutput(
   281  		t, harness, nil,
   282  	)
   283  	spenderTx := chainntnfs.CreateSpendTx(
   284  		t, outpoint, txout, privKey,
   285  	)
   286  	spenderTxHash := spenderTx.TxHash()
   287  	_, err = harness.Node.SendRawTransaction(context.TODO(), spenderTx, true)
   288  	if err != nil {
   289  		t.Fatalf("unable to publish tx: %v", err)
   290  	}
   291  	if err := chainntnfs.WaitForMempoolTx(harness, &spenderTxHash); err != nil {
   292  		t.Fatalf("unable to find tx in the mempool: %v", err)
   293  	}
   294  
   295  	// We'll now confirm this transaction and attempt to retrieve its
   296  	// confirmation details.
   297  	bhs, err := rpctest.AdjustedSimnetMiner(context.Background(), harness.Node, 1)
   298  	if err != nil {
   299  		t.Fatalf("unable to generate block: %v", err)
   300  	}
   301  	block, err := harness.Node.GetBlock(context.TODO(), bhs[0])
   302  	if err != nil {
   303  		t.Fatalf("unable to get block: %v", err)
   304  	}
   305  	var testTx *wire.MsgTx
   306  	for _, tx := range block.Transactions {
   307  		otherHash := tx.TxHash()
   308  		if spenderTxHash.IsEqual(&otherHash) {
   309  			testTx = tx
   310  			break
   311  		}
   312  	}
   313  	if testTx == nil {
   314  		t.Fatalf("test transaction was not mined")
   315  	}
   316  	minedHeight := int64(block.Header.Height)
   317  	prevOutputHeight := minedHeight - 1
   318  
   319  	// Generate a few blocks after mining to test some conditions.
   320  	if _, err := rpctest.AdjustedSimnetMiner(context.Background(), harness.Node, 20); err != nil {
   321  		t.Fatalf("unable to generate block: %v", err)
   322  	}
   323  
   324  	// Store some helper constants.
   325  	endHeight := minedHeight + 20
   326  	pkScript, err := chainscan.ParsePkScript(txout.Version, txout.PkScript)
   327  	if err != nil {
   328  		t.Fatalf("unable to parse pkscript: %v", err)
   329  	}
   330  	_, addrs := stdscript.ExtractAddrs(
   331  		txout.Version, txout.PkScript, netParams,
   332  	)
   333  	if len(addrs) != 1 {
   334  		t.Fatalf("wrong nb of addrs: %d", len(addrs))
   335  	}
   336  	addr := addrs[0]
   337  
   338  	// These are the individual cases to test.
   339  	testCases := []struct {
   340  		name       string
   341  		start      int64
   342  		shouldFind bool
   343  	}{
   344  		{
   345  			name:       "at mined block",
   346  			start:      minedHeight,
   347  			shouldFind: true,
   348  		},
   349  		{
   350  			name:       "long before mined",
   351  			start:      minedHeight - 20,
   352  			shouldFind: true,
   353  		},
   354  		{
   355  			name:       "just before prevout is mined",
   356  			start:      prevOutputHeight - 1,
   357  			shouldFind: true,
   358  		},
   359  		{
   360  			name:       "just before mined",
   361  			start:      minedHeight - 1,
   362  			shouldFind: true,
   363  		},
   364  		{
   365  			name:       "at next block",
   366  			start:      minedHeight + 1,
   367  			shouldFind: false,
   368  		},
   369  		{
   370  			name:       "long after the mined block",
   371  			start:      minedHeight + 20,
   372  			shouldFind: false,
   373  		},
   374  	}
   375  
   376  	// We'll test both scanning for an output and a pkscript for each of
   377  	// the previous tests.
   378  	spendReqTestCases := []struct {
   379  		name      string
   380  		spendReq  chainntnfs.SpendRequest
   381  		addrs     []stdaddr.Address
   382  		outpoints []wire.OutPoint
   383  	}{
   384  		{
   385  			name: "by outpoint",
   386  			spendReq: chainntnfs.SpendRequest{
   387  				OutPoint: *outpoint,
   388  			},
   389  			outpoints: []wire.OutPoint{*outpoint},
   390  		},
   391  		{
   392  			name: "by pkScript",
   393  			spendReq: chainntnfs.SpendRequest{
   394  				PkScript: pkScript,
   395  			},
   396  			addrs: []stdaddr.Address{addr},
   397  		},
   398  	}
   399  
   400  	for _, stc := range spendReqTestCases {
   401  		success := t.Run(stc.name, func(t2 *testing.T) {
   402  			spendReq := stc.spendReq
   403  
   404  			// Load the tx filter with the appropriate outpoint or
   405  			// address as preparation for the tests.
   406  			err := notifier.chainConn.LoadTxFilter(
   407  				context.TODO(), true, stc.addrs, stc.outpoints,
   408  			)
   409  			if err != nil {
   410  				t.Fatalf("unable to build tx filter: %v", err)
   411  			}
   412  
   413  			for _, tc := range testCases {
   414  				success := t2.Run(tc.name, func(t3 *testing.T) {
   415  					histDispatch := chainntnfs.HistoricalSpendDispatch{
   416  						SpendRequest: spendReq,
   417  						StartHeight:  uint32(tc.start),
   418  						EndHeight:    uint32(endHeight),
   419  					}
   420  
   421  					details, err := notifier.inefficientSpendRescan(
   422  						histDispatch.StartHeight, &histDispatch,
   423  					)
   424  
   425  					switch {
   426  					case tc.shouldFind && details == nil:
   427  						t3.Fatalf("should find tx but did not get "+
   428  							"details (%v)", err)
   429  					case !tc.shouldFind && details != nil:
   430  						t3.Fatalf("should not find tx but got details")
   431  					case !tc.shouldFind && err != errInefficientRescanTxNotFound:
   432  						t3.Fatalf("should not find tx but got unexpected error %v", err)
   433  					}
   434  
   435  				})
   436  				if !success {
   437  					break
   438  				}
   439  			}
   440  		})
   441  
   442  		if !success {
   443  			break
   444  		}
   445  	}
   446  }