github.com/decred/dcrlnd@v0.7.6/chainscan/historical_test.go (about) 1 package chainscan 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/decred/dcrd/wire" 9 ) 10 11 type histTestCtx struct { 12 chain *mockChain 13 hist *Historical 14 cancel func() 15 t testingIntf 16 } 17 18 func newHistTestCtx(t testingIntf) *histTestCtx { 19 ctx, cancel := context.WithCancel(context.Background()) 20 chain := newMockChain() 21 hist := NewHistorical(chain) 22 chain.extend(chain.newFromTip()) // Genesis block 23 24 go func() { 25 hist.Run(ctx) 26 }() 27 28 return &histTestCtx{ 29 chain: chain, 30 hist: hist, 31 cancel: cancel, 32 t: t, 33 } 34 } 35 36 func (h *histTestCtx) cleanup() { 37 h.cancel() 38 } 39 40 func (h *histTestCtx) genBlocks(n int, allCfiltersMatch bool) { 41 h.t.Helper() 42 var manglers []blockMangler 43 44 if allCfiltersMatch { 45 manglers = append(manglers, cfilterData(testPkScript)) 46 } 47 48 h.chain.genBlocks(n, manglers...) 49 } 50 51 // TestHistorical tests the basic behavior of the historical scanner against 52 // the scannerTestCases, which must be fulfilled by both the historical and tip 53 // watcher scanners. 54 func TestHistorical(t *testing.T) { 55 56 runTC := func(c scannerTestCase, t *testing.T) { 57 var foundCbEvent, foundChanEvent Event 58 completeChan := make(chan struct{}) 59 foundChan := make(chan Event) 60 tc := newHistTestCtx(t) 61 defer tc.cleanup() 62 63 // The generated test chain is: 64 // - 5 blocks that miss the cfilter match 65 // - 5 blocks with a cfilter match 66 // - block with test case manglers 67 // - 5 blocks with a cfilter match 68 tc.genBlocks(5, false) 69 tc.genBlocks(5, true) 70 b := tc.chain.newFromTip(c.manglers...) 71 tc.chain.extend(b) 72 tc.genBlocks(5, true) 73 74 assertNoError(t, tc.hist.Find( 75 c.target(b), 76 WithFoundCallback(func(e Event, _ FindFunc) { foundCbEvent = e }), 77 WithFoundChan(foundChan), 78 WithCompleteChan(completeChan), 79 )) 80 81 // Wait until the search is complete. 82 assertCompleted(tc.t, completeChan) 83 84 if !c.wantFound { 85 // Testing when we don't expect a match. 86 87 assertFoundChanEmpty(tc.t, foundChan) 88 if foundCbEvent != emptyEvent { 89 t.Fatalf("unexpected foundCallback triggered with %s", &foundCbEvent) 90 } 91 92 // Nothing else to test since we didn't expect a match. 93 return 94 } 95 96 // Testing when we expect a match. 97 98 select { 99 case foundChanEvent = <-foundChan: 100 case <-time.After(5 * time.Second): 101 t.Fatal("found chan not triggered in time") 102 } 103 104 if foundCbEvent == emptyEvent { 105 t.Fatal("foundCallback not triggered") 106 } 107 108 if foundChanEvent != foundCbEvent { 109 t.Fatal("cb and chan showed different events") 110 } 111 112 e := foundChanEvent 113 if e.MatchedField != c.wantMF { 114 t.Fatalf("unexpected matched field. want=%s got=%s", 115 c.wantMF, e.MatchedField) 116 } 117 118 if e.BlockHeight != int32(b.block.Header.Height) { 119 t.Fatalf("unexpected matched block height. want=%d got=%d", 120 b.block.Header.Height, e.BlockHeight) 121 } 122 123 if e.BlockHash != b.block.Header.BlockHash() { 124 t.Fatalf("unexpected matched block hash. want=%s got=%s", 125 b.block.Header.BlockHash(), e.BlockHash) 126 } 127 128 // All tests always match against the first transaction in the 129 // block in either the stake or regular tx tree. 130 var tx *wire.MsgTx 131 var tree int8 132 if len(b.block.Transactions) > 0 { 133 tx = b.block.Transactions[0] 134 tree = wire.TxTreeRegular 135 } else { 136 tx = b.block.STransactions[0] 137 tree = wire.TxTreeStake 138 } 139 if e.Tx.TxHash() != tx.TxHash() { 140 t.Fatalf("unexpected tx match. want=%s got=%s", 141 b.block.Transactions[0].TxHash(), e.Tx.TxHash()) 142 } 143 144 // All tests always match against the second input or output. 145 if e.Index != 1 { 146 t.Fatalf("unexpected index match. want=%d got=%d", 147 1, e.Index) 148 } 149 150 if e.Tree != tree { 151 t.Fatalf("unexpected tree match. want=%d got=%d", 152 tree, e.Tree) 153 } 154 } 155 156 for _, c := range scannerTestCases { 157 c := c 158 ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) }) 159 if !ok { 160 break 161 } 162 } 163 } 164 165 // TestHistoricalCancellation tests that cancelling the search for a target 166 // before it's found makes it actually not get found. 167 func TestHistoricalCancellation(t *testing.T) { 168 completeChan := make(chan struct{}) 169 cancelChan := make(chan struct{}) 170 foundChan := make(chan Event) 171 tc := newHistTestCtx(t) 172 defer tc.cleanup() 173 174 // The generated test chain is: 175 // - 5 blocks that miss the cfilter match 176 // - 5 blocks with a cfilter match 177 // - block with test case manglers 178 // - 5 blocks with a cfilter match 179 tc.genBlocks(5, false) 180 tc.genBlocks(5, true) 181 b := tc.chain.newFromTip( 182 confirmScript(testPkScript), 183 cfilterData(testPkScript), 184 ) 185 tc.chain.extend(b) 186 tc.genBlocks(5, true) 187 188 // Instrument the mock chain so we can stop the search mid-way through. 189 tc.chain.sendNextCfilterChan = make(chan struct{}) 190 191 // Start the search. 192 tc.hist.Find( 193 ConfirmedScript(0, testPkScript), 194 WithFoundChan(foundChan), 195 WithCompleteChan(completeChan), 196 WithCancelChan(cancelChan), 197 ) 198 199 // Allow the first 10 blocks to be scanned. 200 for i := 0; i < 10; i++ { 201 tc.chain.sendNextCfilterChan <- struct{}{} 202 } 203 204 // We don't expect the target to be found yet. 205 select { 206 case <-completeChan: 207 t.Fatal("Unexpected completeChan receive") 208 case <-foundChan: 209 t.Fatal("Unexpected foundChan receive") 210 case <-time.After(10 * time.Millisecond): 211 } 212 213 // Cancel the search. 214 close(cancelChan) 215 216 // The next cfilter may have been requested already, so we allow it to 217 // send (or wait a bit to make sure it wasn't requested). 218 select { 219 case tc.chain.sendNextCfilterChan <- struct{}{}: 220 case <-time.After(10 * time.Millisecond): 221 } 222 223 // We don't expect neither the complete chan, foundChan or new requests 224 // for cfilters to be triggered. 225 select { 226 case <-tc.chain.sendNextCfilterChan: 227 t.Fatal("Unexpected sendNextCfilterChan receive") 228 case <-completeChan: 229 t.Fatal("Unexpected completeChan receive") 230 case <-foundChan: 231 t.Fatal("Unexpected foundChan receive") 232 default: 233 } 234 } 235 236 // TestHistoricalStartHeight tests that starting the search after the height 237 // where the target is found makes it actually not get found. 238 func TestHistoricalStartHeight(t *testing.T) { 239 completeChan := make(chan struct{}) 240 foundChan := make(chan Event) 241 tc := newHistTestCtx(t) 242 defer tc.cleanup() 243 244 // The generated test chain is: 245 // - 5 blocks that miss the cfilter match 246 // - 5 blocks with a cfilter match 247 // - block with test case manglers 248 // - 5 blocks with a cfilter match 249 tc.genBlocks(5, false) 250 tc.genBlocks(5, true) 251 b := tc.chain.newFromTip( 252 confirmScript(testPkScript), 253 cfilterData(testPkScript), 254 ) 255 tc.chain.extend(b) 256 tc.genBlocks(5, true) 257 258 // Start the search. 259 tc.hist.Find( 260 ConfirmedScript(0, testPkScript), 261 WithFoundChan(foundChan), 262 WithCompleteChan(completeChan), 263 WithStartHeight(12), 264 ) 265 266 // The completeChan should be signalled with the completion of the 267 // scan. 268 assertCompleted(t, completeChan) 269 270 // foundChan should not have been triggered. 271 assertFoundChanEmpty(t, foundChan) 272 } 273 274 // TestHistoricalEndHeight tests that stopping the search before the height 275 // where the target is found makes it actually not get found. 276 func TestHistoricalEndHeight(t *testing.T) { 277 completeChan := make(chan struct{}) 278 foundChan := make(chan Event) 279 tc := newHistTestCtx(t) 280 defer tc.cleanup() 281 282 // The generated test chain is: 283 // - 5 blocks that miss the cfilter match 284 // - 5 blocks with a cfilter match 285 // - block with test case manglers 286 // - 5 blocks with a cfilter match 287 tc.genBlocks(5, false) 288 tc.genBlocks(5, true) 289 b := tc.chain.newFromTip( 290 confirmScript(testPkScript), 291 cfilterData(testPkScript), 292 ) 293 tc.chain.extend(b) 294 tc.genBlocks(5, true) 295 296 // Start the search. 297 tc.hist.Find( 298 ConfirmedScript(0, testPkScript), 299 WithFoundChan(foundChan), 300 WithCompleteChan(completeChan), 301 WithEndHeight(int32(b.block.Header.Height-2)), 302 ) 303 304 // The completeChan should be signalled with the completion of the 305 // scan. 306 assertCompleted(t, completeChan) 307 308 // foundChan should not have been triggered. 309 assertFoundChanEmpty(t, foundChan) 310 } 311 312 // TestHistoricalMultipleMatchesInBlock tests that the historical search 313 // correctly sends multiple events when the same script is confirmed multiple 314 // times in a single block. 315 func TestHistoricalMultipleMatchesInBlock(t *testing.T) { 316 completeChan := make(chan struct{}) 317 foundChan := make(chan Event) 318 tc := newHistTestCtx(t) 319 defer tc.cleanup() 320 321 // The generated test chain is: 322 // - 5 blocks that miss the cfilter match 323 // - 5 blocks with a cfilter match 324 // - block with test case manglers 325 // - 5 blocks with a cfilter match 326 tc.genBlocks(5, false) 327 tc.genBlocks(5, true) 328 b := tc.chain.newFromTip( 329 confirmScript(testPkScript), 330 cfilterData(testPkScript), 331 ) 332 // Create an additional output. 333 b.block.Transactions[0].AddTxOut(&wire.TxOut{PkScript: testPkScript}) 334 335 tc.chain.extend(b) 336 tc.genBlocks(5, true) 337 338 // Start the search. 339 tc.hist.Find( 340 ConfirmedScript(0, testPkScript), 341 WithFoundChan(foundChan), 342 WithCompleteChan(completeChan), 343 ) 344 345 // The completeChan should be signalled with the completion of the 346 // scan. 347 assertCompleted(t, completeChan) 348 349 // foundChan should be triggered two (and only two) times. 350 e1 := assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height)) 351 e2 := assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height)) 352 assertFoundChanEmpty(t, foundChan) 353 354 // However the events should *not* be exactly the same: the script was 355 // confirmed in two different outputs. 356 if e1 == e2 { 357 t.Fatal("script confirmed twice in the same output") 358 } 359 } 360 361 // TestHistoricalBlockDownload tests that the historical search only downloads 362 // blocks for which the cfilter has passed. 363 func TestHistoricalBlockDownload(t *testing.T) { 364 completeChan := make(chan struct{}) 365 tc := newHistTestCtx(t) 366 defer tc.cleanup() 367 368 // The generated test chain is: 369 // - 5 blocks that miss the cfilter match 370 // - 5 blocks with a cfilter match 371 // - block with test case manglers 372 // - 5 blocks with a cfilter match 373 tc.genBlocks(5, false) 374 tc.genBlocks(5, true) 375 b := tc.chain.newFromTip( 376 confirmScript(testPkScript), 377 cfilterData(testPkScript), 378 ) 379 tc.chain.extend(b) 380 tc.genBlocks(5, true) 381 382 // Perform the full search. 383 tc.hist.Find( 384 ConfirmedScript(0, testPkScript), 385 WithCompleteChan(completeChan), 386 ) 387 assertCompleted(t, completeChan) 388 389 // We only expect fetches for 11 blocks of data. 390 wantGetBlockCount := uint32(11) 391 if tc.chain.getBlockCount != wantGetBlockCount { 392 t.Fatalf("Unexpected getBlockCount. want=%d got=%d", 393 wantGetBlockCount, tc.chain.getBlockCount) 394 } 395 } 396 397 // TestHistoricalMultipleFinds tests that performing a search with multiple 398 // finds for the same target works as expected. 399 func TestHistoricalMultipleFinds(t *testing.T) { 400 401 runTC := func(c scannerTestCase, t *testing.T) { 402 tc := newHistTestCtx(t) 403 defer tc.cleanup() 404 405 // The generated test chain is: 406 // - 5 blocks that miss the cfilter match 407 // - 5 blocks with a cfilter match 408 // - block with test case manglers 409 // - 5 blocks with a cfilter match 410 tc.genBlocks(5, false) 411 tc.genBlocks(5, true) 412 b := tc.chain.newFromTip(c.manglers...) 413 tc.chain.extend(b) 414 tc.genBlocks(5, true) 415 416 foundChan1 := make(chan Event) 417 foundChan2 := make(chan Event) 418 419 // Start the search. 420 tc.hist.FindMany([]TargetAndOptions{ 421 { 422 Target: c.target(b), 423 Options: []Option{ 424 WithFoundChan(foundChan1), 425 }, 426 }, 427 { 428 Target: c.target(b), 429 Options: []Option{ 430 WithFoundChan(foundChan2), 431 }, 432 }, 433 }) 434 435 // We expect one (and only one) event in each foundChan. 436 assertFoundChanRcvHeight(tc.t, foundChan1, int32(b.block.Header.Height)) 437 assertFoundChanRcvHeight(tc.t, foundChan2, int32(b.block.Header.Height)) 438 assertFoundChanEmpty(tc.t, foundChan1) 439 assertFoundChanEmpty(tc.t, foundChan2) 440 } 441 442 // Test against all variants of targets. 443 for _, c := range scannerTestCases { 444 if !c.wantFound { 445 continue 446 } 447 c := c 448 ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) }) 449 if !ok { 450 break 451 } 452 } 453 } 454 455 // TestHistoricalMultipleOverlap tests that starting a search with multiple 456 // targets with overlapping search intervals works as expected. 457 func TestHistoricalMultipleOverlap(t *testing.T) { 458 459 runTC := func(c scannerTestCase, t *testing.T) { 460 tc := newHistTestCtx(t) 461 defer tc.cleanup() 462 463 // The generated test chain is: 464 // - 5 blocks that miss the cfilter match 465 // - 5 blocks with a cfilter match 466 // - block with test case manglers 467 // - 5 blocks with a cfilter match 468 // - block with test case manglers 469 tc.genBlocks(5, false) 470 tc.genBlocks(5, true) 471 b := tc.chain.newFromTip(c.manglers...) 472 tc.chain.extend(b) 473 tc.genBlocks(5, true) 474 b2 := tc.chain.newFromTip(c.manglers...) 475 tc.chain.extend(b2) 476 477 foundChan1 := make(chan Event) 478 foundChan2 := make(chan Event) 479 completeChan1 := make(chan struct{}) 480 completeChan2 := make(chan struct{}) 481 482 // Start two concurrent searches with non-overlapping heights. 483 tc.hist.FindMany([]TargetAndOptions{ 484 { 485 Target: c.target(b), 486 Options: []Option{ 487 WithFoundChan(foundChan1), 488 WithCompleteChan(completeChan1), 489 WithEndHeight(int32(b.block.Header.Height + 2)), 490 }, 491 }, 492 { 493 Target: c.target(b2), 494 Options: []Option{ 495 WithFoundChan(foundChan2), 496 WithCompleteChan(completeChan2), 497 WithStartHeight(int32(b.block.Header.Height + 1)), 498 }, 499 }, 500 }) 501 502 // Wait for both searches to complete. 503 assertCompleted(tc.t, completeChan1) 504 assertCompleted(tc.t, completeChan2) 505 506 // We expect one (and only one) signall in each foundChan. 507 assertFoundChanRcvHeight(tc.t, foundChan1, int32(b.block.Header.Height)) 508 assertFoundChanRcvHeight(tc.t, foundChan2, int32(b2.block.Header.Height)) 509 assertFoundChanEmpty(tc.t, foundChan1) 510 assertFoundChanEmpty(tc.t, foundChan2) 511 } 512 513 // Test against all variants of targets. 514 for _, c := range scannerTestCases { 515 if !c.wantFound { 516 continue 517 } 518 c := c 519 ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) }) 520 if !ok { 521 break 522 } 523 } 524 } 525 526 // TestHistoricalMultipleNoOverlap tests that starting a search with multiple, 527 // non-overlapping targets works as expected. 528 func TestHistoricalMultipleNoOverlap(t *testing.T) { 529 530 runTC := func(c scannerTestCase, t *testing.T) { 531 tc := newHistTestCtx(t) 532 defer tc.cleanup() 533 534 // The generated test chain is: 535 // - 5 blocks that miss the cfilter match 536 // - 5 blocks with a cfilter match 537 // - block with test case manglers 538 // - 5 blocks with a cfilter match 539 // - block with test case manglers 540 tc.genBlocks(5, false) 541 tc.genBlocks(5, true) 542 b := tc.chain.newFromTip(c.manglers...) 543 tc.chain.extend(b) 544 tc.genBlocks(5, true) 545 b2 := tc.chain.newFromTip(c.manglers...) 546 tc.chain.extend(b2) 547 548 foundChan1 := make(chan Event) 549 foundChan2 := make(chan Event) 550 completeChan1 := make(chan struct{}) 551 completeChan2 := make(chan struct{}) 552 553 // Start two concurrent searches with non-overlapping heights. 554 tc.hist.FindMany([]TargetAndOptions{ 555 { 556 Target: c.target(b), 557 Options: []Option{ 558 WithFoundChan(foundChan1), 559 WithCompleteChan(completeChan1), 560 WithEndHeight(int32(b.block.Header.Height + 1)), 561 }, 562 }, 563 { 564 Target: c.target(b2), 565 Options: []Option{ 566 WithFoundChan(foundChan2), 567 WithCompleteChan(completeChan2), 568 WithStartHeight(int32(b.block.Header.Height + 4)), 569 }, 570 }, 571 }) 572 573 // Wait for both searches to complete. 574 assertCompleted(tc.t, completeChan1) 575 assertCompleted(tc.t, completeChan2) 576 577 // We expect one (and only one) signall in each foundChan. 578 assertFoundChanRcvHeight(tc.t, foundChan1, int32(b.block.Header.Height)) 579 assertFoundChanRcvHeight(tc.t, foundChan2, int32(b2.block.Header.Height)) 580 assertFoundChanEmpty(tc.t, foundChan1) 581 assertFoundChanEmpty(tc.t, foundChan2) 582 } 583 584 // Test against all variants of targets. 585 for _, c := range scannerTestCases { 586 if !c.wantFound { 587 continue 588 } 589 c := c 590 ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) }) 591 if !ok { 592 break 593 } 594 } 595 } 596 597 // TestHistoricalAddNewTargetDuringFcb tests that adding new targets for the 598 // historical search during the call for FoundCallback by using the passed 599 // function works as expected and provokes the new target to be found. 600 func TestHistoricalAddNewTargetDuringFcb(t *testing.T) { 601 602 runTC := func(c scannerTestCase, t *testing.T) { 603 tc := newHistTestCtx(t) 604 defer tc.cleanup() 605 606 // The generated test chain is: 607 // - 5 blocks that miss the cfilter match 608 // - 5 blocks with a cfilter match 609 // - block with test case manglers (twice) 610 // - 5 blocks with a cfilter match 611 tc.genBlocks(5, false) 612 tc.genBlocks(5, true) 613 b := tc.chain.newFromTip(c.manglers...) 614 dupeTestTx(b) 615 tc.chain.extend(b) 616 tc.genBlocks(5, true) 617 618 foundChan := make(chan Event) 619 foundCb := func(e Event, addNew FindFunc) { 620 assertNoError(t, addNew( 621 c.target(b), 622 WithStartHeight(e.BlockHeight+1), 623 WithFoundChan(foundChan), 624 )) 625 } 626 627 // Start the search. 628 tc.hist.Find( 629 c.target(b), 630 WithFoundCallback(foundCb), 631 ) 632 633 // We expect one (and only one) event in foundChan 634 assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height)) 635 assertFoundChanEmpty(t, foundChan) 636 637 } 638 // Test against all variants of targets. 639 for _, c := range scannerTestCases { 640 if !c.wantFound { 641 continue 642 } 643 c := c 644 ok := t.Run(c.name, func(t *testing.T) { runTC(c, t) }) 645 if !ok { 646 break 647 } 648 } 649 } 650 651 // TestHistoricalAddNewTarget tests that adding new targets for the historical 652 // search during the call for FoundCallback works as expected and provokes the 653 // new target to be found. 654 func TestHistoricalAddNewTarget(t *testing.T) { 655 tc := newHistTestCtx(t) 656 defer tc.cleanup() 657 658 pkScript2 := []byte{0x01, 0x02, 0x03, 0x04} 659 660 // The generated test chain is: 661 // - 5 blocks that miss the cfilter match 662 // - 5 blocks with a cfilter match 663 // - block with test case manglers 664 // - 5 blocks with a cfilter match 665 // - block with second confirmed pkscript 666 tc.genBlocks(5, false) 667 tc.genBlocks(5, true) 668 b := tc.chain.newFromTip( 669 confirmScript(testPkScript), 670 cfilterData(testPkScript), 671 ) 672 tc.chain.extend(b) 673 tc.genBlocks(5, true) 674 b2 := tc.chain.newFromTip( 675 confirmScript(pkScript2), 676 cfilterData(pkScript2), 677 ) 678 tc.chain.extend(b2) 679 680 foundChanOld := make(chan Event) 681 foundChanNew := make(chan Event) 682 foundChanRestart := make(chan Event) 683 foundCb := func(e Event, _ FindFunc) { 684 // The foundCallback will start a new search for three 685 // different targets: 686 // - The testPkScript with startHeight of e.BlockHeight+1 which 687 // shouldn't match; 688 // - The pkScript2 with startHeight of e.BlockHeight+1 which 689 // should match; 690 // - The testPkScript with startHeight of 0 which should match. 691 tc.hist.Find( 692 ConfirmedScript(0, testPkScript), 693 WithFoundChan(foundChanOld), 694 WithStartHeight(e.BlockHeight+1), 695 ) 696 tc.hist.Find( 697 ConfirmedScript(0, pkScript2), 698 WithFoundChan(foundChanNew), 699 WithStartHeight(e.BlockHeight+1), 700 ) 701 tc.hist.Find( 702 ConfirmedScript(0, testPkScript), 703 WithFoundChan(foundChanRestart), 704 ) 705 } 706 707 // Start the search. 708 tc.hist.Find( 709 ConfirmedScript(0, testPkScript), 710 WithFoundCallback(foundCb), 711 ) 712 713 // foundChanOld should be empty since the search was started at a block 714 // height higher than where the script was confirmed. The other two 715 // channels should have confirmations. 716 assertFoundChanEmpty(t, foundChanOld) 717 assertFoundChanRcvHeight(t, foundChanNew, int32(b2.block.Header.Height)) 718 assertFoundChanRcvHeight(t, foundChanRestart, int32(b.block.Header.Height)) 719 } 720 721 // TestHistoricalAddNewTargetSingleBatch tests that adding new targets for the 722 // historical search during the call for FoundCallback which doesn't lead to a 723 // new batch correctly causes only the current batch to be executed. 724 func TestHistoricalAddNewTargetSingleBatch(t *testing.T) { 725 tc := newHistTestCtx(t) 726 defer tc.cleanup() 727 728 // The generated test chain is: 729 // - 5 blocks that miss the cfilter match 730 // - 5 blocks with a cfilter match 731 // - block with test case manglers 732 // - 5 blocks with a cfilter match 733 // - block with second confirmed pkscript 734 tc.genBlocks(5, false) 735 tc.genBlocks(5, true) 736 b := tc.chain.newFromTip( 737 confirmScript(testPkScript), 738 cfilterData(testPkScript), 739 ) 740 tc.chain.extend(b) 741 tc.genBlocks(5, true) 742 743 completeChan := make(chan struct{}) 744 foundCb := func(e Event, _ FindFunc) { 745 // Add the new target with a start height of currentHeight+1 so 746 // that it will be added to the current batch. 747 tc.hist.Find( 748 ConfirmedScript(0, testPkScript), 749 WithCompleteChan(completeChan), 750 WithStartHeight(e.BlockHeight+1), 751 ) 752 } 753 754 // Start the search. 755 tc.hist.Find( 756 ConfirmedScript(0, testPkScript), 757 WithFoundCallback(foundCb), 758 ) 759 760 assertCompleted(t, completeChan) 761 762 // We expect only a single batch to have taken place, so all blocks 763 // should have been downloaded only once. 764 wantGetBlockCount := uint32(11) 765 if tc.chain.getBlockCount != wantGetBlockCount { 766 t.Fatalf("Unexpected getBlockCount. want=%d got=%d", 767 wantGetBlockCount, tc.chain.getBlockCount) 768 } 769 } 770 771 // TestHistoricalNewTipNoOverlap tests that performing a historical search when 772 // new tips of the chain are coming in behaves as expected when we create a new 773 // search that does not overlap with the existing search. 774 func TestHistoricalNewTipNoOverlap(t *testing.T) { 775 tc := newHistTestCtx(t) 776 defer tc.cleanup() 777 778 // Instrument the mock chain so we can stop the search half-way 779 // through. 780 tc.chain.sendNextCfilterChan = make(chan struct{}) 781 782 // The generated test chain is: 783 // - 5 blocks that miss the cfilter match 784 // - 5 blocks with a cfilter match 785 // - block with test case manglers 786 // - 5 blocks with a cfilter match 787 // - block with second confirmed pkscript 788 tc.genBlocks(5, false) 789 tc.genBlocks(5, true) 790 b := tc.chain.newFromTip( 791 confirmScript(testPkScript), 792 cfilterData(testPkScript), 793 ) 794 tc.chain.extend(b) 795 tc.genBlocks(5, true) 796 797 // Start the search. 798 foundChan := make(chan Event) 799 tc.hist.Find( 800 ConfirmedScript(0, testPkScript), 801 WithFoundChan(foundChan), 802 ) 803 804 // Let it process 3 blocks and start processing the fourth. 805 tc.chain.sendNextCfilterChan <- struct{}{} 806 tc.chain.sendNextCfilterChan <- struct{}{} 807 tc.chain.sendNextCfilterChan <- struct{}{} 808 809 // Let the blocks be processed. 810 time.Sleep(10 * time.Millisecond) 811 812 // Extend the tip with 3 new blocks, including a match of the target at 813 // the end. 814 startHeight := int32(tc.chain.tip.block.Header.Height) + 1 815 tc.genBlocks(2, true) 816 b2 := tc.chain.newFromTip( 817 confirmScript(testPkScript), 818 cfilterData(testPkScript), 819 ) 820 tc.chain.extend(b2) 821 822 // Start a new search which does not overlap with the previous search. 823 // The start height will be higher than the previous search's end 824 // height. 825 foundChanNew := make(chan Event) 826 completeChanNew := make(chan struct{}) 827 tc.hist.Find( 828 ConfirmedScript(0, testPkScript), 829 WithFoundChan(foundChanNew), 830 WithStartHeight(startHeight), 831 WithCompleteChan(completeChanNew), 832 ) 833 834 // Send as many blocks as needed until no more are requested by the 835 // scan. 836 done := false 837 for !done { 838 select { 839 case tc.chain.sendNextCfilterChan <- struct{}{}: 840 case <-time.After(10 * time.Millisecond): 841 done = true 842 } 843 } 844 845 // Wait for the second scan to wrap up. 846 assertCompleted(tc.t, completeChanNew) 847 848 // We expect one (and only one) signal in each foundChan. 849 assertFoundChanRcvHeight(tc.t, foundChan, int32(b.block.Header.Height)) 850 assertFoundChanRcvHeight(tc.t, foundChanNew, int32(b2.block.Header.Height)) 851 assertFoundChanEmpty(tc.t, foundChan) 852 assertFoundChanEmpty(tc.t, foundChanNew) 853 } 854 855 // TestHistoricalNewTipOverlap tests that performing a historical search when 856 // new tips of the chain are coming in behaves as expected when we create a new 857 // search that overlaps with the existing search. 858 func TestHistoricalNewTipOverlap(t *testing.T) { 859 tc := newHistTestCtx(t) 860 defer tc.cleanup() 861 862 // Instrument the mock chain so we can stop the search mid-way through. 863 tc.chain.sendNextCfilterChan = make(chan struct{}) 864 865 // The generated test chain is: 866 // - 5 blocks that miss the cfilter match 867 // - 5 blocks with a cfilter match 868 // - block with test case manglers 869 // - 5 blocks with a cfilter match 870 // - block with second confirmed pkscript 871 tc.genBlocks(5, false) 872 tc.genBlocks(5, true) 873 b := tc.chain.newFromTip( 874 confirmScript(testPkScript), 875 cfilterData(testPkScript), 876 ) 877 tc.chain.extend(b) 878 tc.genBlocks(5, true) 879 880 // Start the search. 881 foundChan := make(chan Event) 882 tc.hist.Find( 883 ConfirmedScript(0, testPkScript), 884 WithFoundChan(foundChan), 885 ) 886 887 // Let it process 3 blocks and start processing the fourth. 888 tc.chain.sendNextCfilterChan <- struct{}{} 889 tc.chain.sendNextCfilterChan <- struct{}{} 890 tc.chain.sendNextCfilterChan <- struct{}{} 891 892 // Let the blocks be processed. 893 time.Sleep(10 * time.Millisecond) 894 895 // Extend the tip with 3 new blocks, including a match of the target at 896 // the end. 897 startHeight := int32(13) 898 tc.genBlocks(2, true) 899 b2 := tc.chain.newFromTip( 900 confirmScript(testPkScript), 901 cfilterData(testPkScript), 902 ) 903 tc.chain.extend(b2) 904 905 // Start a new search which overlaps with the previous search. The 906 // start height will be lower than the previous search's end height and 907 // its current height. 908 foundChanNew := make(chan Event) 909 completeChanNew := make(chan struct{}) 910 tc.hist.Find( 911 ConfirmedScript(0, testPkScript), 912 WithFoundChan(foundChanNew), 913 WithStartHeight(startHeight), 914 WithCompleteChan(completeChanNew), 915 ) 916 917 // Send as many blocks as needed until no more are requested by the 918 // scan. 919 done := false 920 for !done { 921 select { 922 case tc.chain.sendNextCfilterChan <- struct{}{}: 923 case <-time.After(10 * time.Millisecond): 924 done = true 925 } 926 } 927 928 // Wait for the second scan to wrap up. 929 assertCompleted(tc.t, completeChanNew) 930 931 // We expect one (and only one) signal in each foundChan. 932 assertFoundChanRcvHeight(tc.t, foundChan, int32(b.block.Header.Height)) 933 assertFoundChanRcvHeight(tc.t, foundChanNew, int32(b2.block.Header.Height)) 934 assertFoundChanEmpty(tc.t, foundChan) 935 assertFoundChanEmpty(tc.t, foundChanNew) 936 937 // We only expect one fetch for each (cfilter matched) block. 938 wantGetBlockCount := uint32(14) 939 if tc.chain.getBlockCount != wantGetBlockCount { 940 t.Fatalf("Unexpected getBlockCount. want=%d got=%d", 941 wantGetBlockCount, tc.chain.getBlockCount) 942 } 943 } 944 945 // TestHistoricalAfterTip tests that attempting to scan past the current chain 946 // tip works as expected. 947 func TestHistoricalAfterTip(t *testing.T) { 948 tc := newHistTestCtx(t) 949 defer tc.cleanup() 950 951 // The generated test chain is: 952 // - 5 blocks that miss the cfilter match 953 // - 5 blocks with a cfilter match 954 // - block with test case manglers 955 // - 5 blocks with a cfilter match 956 // - block with second confirmed pkscript 957 tc.genBlocks(5, false) 958 tc.genBlocks(5, true) 959 b := tc.chain.newFromTip( 960 confirmScript(testPkScript), 961 cfilterData(testPkScript), 962 ) 963 tc.chain.extend(b) 964 tc.genBlocks(5, true) 965 966 completeChan := make(chan struct{}) 967 foundChan := make(chan Event) 968 969 // Start the search with an EndHeight past the tip. The mock chain 970 // returns ErrBlockAfterTip in this situation. 971 tc.hist.Find( 972 ConfirmedScript(0, testPkScript), 973 WithFoundChan(foundChan), 974 WithCompleteChan(completeChan), 975 WithEndHeight(int32(tc.chain.tip.block.Header.Height)+1), 976 ) 977 978 assertCompleted(t, completeChan) 979 assertFoundChanRcvHeight(t, foundChan, int32(b.block.Header.Height)) 980 } 981 982 // TestHistoricalFindSpendAfterConfirm asserts that attempting to find a spent 983 // UTXO works when the spend happens on a later block and the spent is added 984 // during the search for confirmation. 985 func TestHistoricalFindSpendAfterConfirm(t *testing.T) { 986 tc := newHistTestCtx(t) 987 defer tc.cleanup() 988 989 // The generated test chain is: 990 // - 5 blocks that miss the cfilter match 991 // - 5 blocks with a cfilter match 992 // - block with confirmed output 993 // - 5 blocks with a cfilter match 994 // - 5 blocks that miss the cfilter match 995 // - block which spends the previous output 996 tc.genBlocks(5, false) 997 tc.genBlocks(5, true) 998 bConfirm := tc.chain.newFromTip( 999 confirmScript(testPkScript), 1000 cfilterData(testPkScript), 1001 ) 1002 outp := wire.OutPoint{ 1003 Hash: bConfirm.block.Transactions[0].TxHash(), 1004 Index: 1, 1005 } 1006 tc.chain.extend(bConfirm) 1007 tc.genBlocks(5, true) 1008 tc.genBlocks(5, false) 1009 bSpend := tc.chain.newFromTip( 1010 spendOutPoint(outp), 1011 cfilterData(testPkScript), 1012 ) 1013 tc.chain.extend(bSpend) 1014 1015 // Setup the callback that is called when the output is confirmed and 1016 // which will trigger the search for the spent outpoint. 1017 foundSpentChan := make(chan Event) 1018 completeChan := make(chan struct{}) 1019 foundCb := func(e Event, addNew FindFunc) { 1020 assertNoError(t, addNew( 1021 SpentOutPoint(outp, 0, testPkScript), 1022 WithStartHeight(e.BlockHeight+1), 1023 WithFoundChan(foundSpentChan), 1024 WithCompleteChan(completeChan), 1025 )) 1026 } 1027 1028 // Start the search. 1029 tc.hist.Find( 1030 ConfirmedScript(0, testPkScript), 1031 WithFoundCallback(foundCb), 1032 ) 1033 1034 // Search should've been completed and the spending tx found. 1035 assertCompleted(t, completeChan) 1036 assertFoundChanRcvHeight(t, foundSpentChan, int32(bSpend.block.Header.Height)) 1037 } 1038 1039 // BenchHistoricalCfilterMisses benchmarks the behavior of historical searches 1040 // when most/all blocks cause a cfilter check to miss (that is, full blocks 1041 // aren't downloaded and tested individually). 1042 // 1043 // Reported time and allocation count should be interpreted as per-block in the 1044 // blockchain. 1045 func BenchmarkHistoricalCfilterMisses(b *testing.B) { 1046 1047 runTC := func(c scannerTestCase, b *testing.B) { 1048 b.ReportAllocs() 1049 tc := newHistTestCtx(b) 1050 defer tc.cleanup() 1051 1052 // Dummy block (will never match as we won't add it to the 1053 // chain). 1054 bl := tc.chain.newFromTip(c.manglers...) 1055 1056 // Generate N blocks without cfilter matches. 1057 tc.genBlocks(b.N, false) 1058 completeChan := make(chan struct{}) 1059 foundChan := make(chan Event) 1060 1061 targets := make([]TargetAndOptions, 1) 1062 targets[0] = TargetAndOptions{ 1063 Target: c.target(bl), 1064 Options: []Option{ 1065 WithCompleteChan(completeChan), 1066 WithFoundChan(foundChan), 1067 }, 1068 } 1069 1070 // Reset the benchmark to this point. 1071 b.ResetTimer() 1072 1073 // Find all items and wait for them to complete. 1074 tc.hist.FindMany(targets) 1075 select { 1076 case <-foundChan: 1077 b.Fatal("Unexpected event in foundChan") 1078 case <-completeChan: 1079 case <-time.After(60 * time.Second): 1080 b.Fatal("Timeout in benchmark") 1081 } 1082 } 1083 1084 // Test against all variants of targets. 1085 for _, c := range scannerTestCases { 1086 if !c.wantFound { 1087 continue 1088 } 1089 c := c 1090 ok := b.Run(c.name, func(b *testing.B) { runTC(c, b) }) 1091 if !ok { 1092 break 1093 } 1094 } 1095 } 1096 1097 // BenchHistoricalMatches benchmarks the behavior of historical searches when 1098 // most/all blocks cause a match in the block itself. 1099 // 1100 // Reported time and allocation counts should be interpreted as per-(1 input + 1101 // 1 output) in the blockchain. 1102 // 1103 // Note: This benchmark only runs for testcases which might match multiple 1104 // blocks (i.e., only match by script vs outpoint). 1105 func BenchmarkHistoricalMatches(b *testing.B) { 1106 1107 runTC := func(c scannerTestCase, b *testing.B) { 1108 b.ReportAllocs() 1109 tc := newHistTestCtx(b) 1110 defer tc.cleanup() 1111 1112 bl := tc.chain.newFromTip(c.manglers...) 1113 1114 // Generate N blocks with block matches. 1115 tc.chain.genBlocks(b.N, c.manglers...) 1116 completeChan := make(chan struct{}) 1117 foundChan := make(chan Event) 1118 var cbCount int 1119 foundCb := func(_ Event, _ FindFunc) { 1120 cbCount++ 1121 } 1122 1123 // Drain foundChan until completeChan is closed. 1124 go func() { 1125 for { 1126 select { 1127 case <-foundChan: 1128 case <-completeChan: 1129 return 1130 } 1131 } 1132 }() 1133 1134 targets := make([]TargetAndOptions, 1) 1135 targets[0] = TargetAndOptions{ 1136 Target: c.target(bl), 1137 Options: []Option{ 1138 WithCompleteChan(completeChan), 1139 WithFoundCallback(foundCb), 1140 WithFoundChan(foundChan), 1141 }, 1142 } 1143 1144 // Reset the benchmark to this point. 1145 b.ResetTimer() 1146 1147 // Find all items and wait for them to complete. 1148 tc.hist.FindMany(targets) 1149 select { 1150 case <-completeChan: 1151 /* 1152 if cbCount != b.N { 1153 b.Fatalf("Different number of callback calls. want=%d got=%d", 1154 b.N, cbCount) 1155 } 1156 */ 1157 case <-time.After(60 * time.Second): 1158 b.Fatal("Timeout in benchmark") 1159 } 1160 } 1161 1162 // Test against all variants of targets. 1163 for _, c := range scannerTestCases { 1164 // The only test cases amenable to this benchmark are those 1165 // that use a fixed script or outpoint for matching. 1166 // 1167 // Returning multiple matches against a specific spent outpoint 1168 // isn't really something that should happen in the blockchain 1169 // but is useful as a benchmark method. 1170 if c.name != "SpentScript" && c.name != "ConfirmedScript" && c.name != "SpentOutPoint" { 1171 continue 1172 } 1173 1174 c := c 1175 ok := b.Run(c.name, func(b *testing.B) { runTC(c, b) }) 1176 if !ok { 1177 break 1178 } 1179 } 1180 } 1181 1182 // BenchHistorical benchmarks the behavior of historical searches when most/all 1183 // blocks cause a cfilter check to match (that is, full blocks are downloaded 1184 // and tested individually). 1185 // 1186 // Reported time and allocation should be interpreted as per-(1 input + 1 1187 // output) in the blockchain. 1188 func BenchmarkHistorical(b *testing.B) { 1189 1190 runTC := func(c scannerTestCase, b *testing.B) { 1191 b.ReportAllocs() 1192 tc := newHistTestCtx(b) 1193 defer tc.cleanup() 1194 1195 // Dummy block (will never match as we won't add it to the 1196 // chain). 1197 bl := tc.chain.newFromTip(c.manglers...) 1198 1199 // Generate N blocks with cfilter matches. 1200 tc.genBlocks(b.N, true) 1201 completeChan := make(chan struct{}) 1202 foundChan := make(chan Event) 1203 1204 targets := make([]TargetAndOptions, 1) 1205 targets[0] = TargetAndOptions{ 1206 Target: c.target(bl), 1207 Options: []Option{ 1208 WithCompleteChan(completeChan), 1209 WithFoundChan(foundChan), 1210 }, 1211 } 1212 1213 // Reset the benchmark to this point. 1214 b.ResetTimer() 1215 1216 // Find all items and wait for them to complete. 1217 tc.hist.FindMany(targets) 1218 select { 1219 case <-foundChan: 1220 b.Fatal("Unexpected event in foundChan") 1221 case <-completeChan: 1222 case <-time.After(60 * time.Second): 1223 b.Fatal("Timeout in benchmark") 1224 } 1225 } 1226 1227 // Test against all variants of targets. 1228 for _, c := range scannerTestCases { 1229 if !c.wantFound { 1230 continue 1231 } 1232 c := c 1233 ok := b.Run(c.name, func(b *testing.B) { runTC(c, b) }) 1234 if !ok { 1235 break 1236 } 1237 } 1238 }