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 }