Tests that blocks sent to the fetcher (either through propagation or via hash announces and retrievals) don't pile up indefinitely, exhausting available system memory.
(t *testing.T)
| 688 | // announces and retrievals) don't pile up indefinitely, exhausting available |
| 689 | // system memory. |
| 690 | func TestBlockMemoryExhaustionAttack(t *testing.T) { |
| 691 | // Create a tester with instrumented import hooks |
| 692 | tester := newTester() |
| 693 | |
| 694 | imported, enqueued := make(chan *types.Block), int32(0) |
| 695 | tester.fetcher.importedHook = func(block *types.Block) { imported <- block } |
| 696 | tester.fetcher.queueChangeHook = func(hash common.Hash, added bool) { |
| 697 | if added { |
| 698 | atomic.AddInt32(&enqueued, 1) |
| 699 | } else { |
| 700 | atomic.AddInt32(&enqueued, -1) |
| 701 | } |
| 702 | } |
| 703 | // Create a valid chain and a batch of dangling (but in range) blocks |
| 704 | targetBlocks := hashLimit + 2*maxQueueDist |
| 705 | hashes, blocks := makeChain(targetBlocks, 0, genesis) |
| 706 | attack := make(map[common.Hash]*types.Block) |
| 707 | for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ { |
| 708 | hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock) |
| 709 | for _, hash := range hashes[:maxQueueDist-2] { |
| 710 | attack[hash] = blocks[hash] |
| 711 | } |
| 712 | } |
| 713 | // Try to feed all the attacker blocks make sure only a limited batch is accepted |
| 714 | for _, block := range attack { |
| 715 | tester.fetcher.Enqueue("attacker", block) |
| 716 | } |
| 717 | time.Sleep(200 * time.Millisecond) |
| 718 | if queued := atomic.LoadInt32(&enqueued); queued != blockLimit { |
| 719 | t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit) |
| 720 | } |
| 721 | // Queue up a batch of valid blocks, and check that a new peer is allowed to do so |
| 722 | for i := 0; i < maxQueueDist-1; i++ { |
| 723 | tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]]) |
| 724 | } |
| 725 | time.Sleep(100 * time.Millisecond) |
| 726 | if queued := atomic.LoadInt32(&enqueued); queued != blockLimit+maxQueueDist-1 { |
| 727 | t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1) |
| 728 | } |
| 729 | // Insert the missing piece (and sanity check the import) |
| 730 | tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2]]) |
| 731 | verifyImportCount(t, imported, maxQueueDist) |
| 732 | |
| 733 | // Insert the remaining blocks in chunks to ensure clean DOS protection |
| 734 | for i := maxQueueDist; i < len(hashes)-1; i++ { |
| 735 | tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2-i]]) |
| 736 | verifyImportEvent(t, imported, true) |
| 737 | } |
| 738 | verifyImportDone(t, imported) |
| 739 | } |
nothing calls this directly
no test coverage detected