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 }