github.com/beyonderyue/gochain@v2.2.26+incompatible/eth/fetcher/fetcher_test.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package fetcher 18 19 import ( 20 "context" 21 "errors" 22 "math/big" 23 "sync" 24 "sync/atomic" 25 "testing" 26 "time" 27 28 "github.com/gochain-io/gochain/common" 29 "github.com/gochain-io/gochain/consensus/clique" 30 "github.com/gochain-io/gochain/core" 31 "github.com/gochain-io/gochain/core/types" 32 "github.com/gochain-io/gochain/crypto" 33 "github.com/gochain-io/gochain/ethdb" 34 "github.com/gochain-io/gochain/params" 35 ) 36 37 var ( 38 testdb = ethdb.NewMemDatabase() 39 testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 40 testAddress = crypto.PubkeyToAddress(testKey.PublicKey) 41 genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) 42 unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit, Signer: genesis.Signer()}, nil, nil, nil) 43 ) 44 45 // makeChain creates a chain of n blocks starting at and including parent. 46 // the returned hash chain is ordered head->parent. In addition, every 3rd block 47 // contains a transaction and every 5th an uncle to allow testing correct block 48 // reassembly. 49 func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { 50 ctx := context.Background() 51 blocks, _ := core.GenerateChain(ctx, params.TestChainConfig, parent, clique.NewFaker(), testdb, n, func(ctx context.Context, i int, block *core.BlockGen) { 52 block.SetCoinbase(common.Address{seed}) 53 54 // If the block number is multiple of 3, send a bonus transaction to the miner 55 if parent == genesis && i%3 == 0 { 56 signer := types.MakeSigner(params.TestChainConfig, block.Number()) 57 tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) 58 if err != nil { 59 panic(err) 60 } 61 block.AddTx(ctx, tx) 62 } 63 }) 64 hashes := make([]common.Hash, n+1) 65 hashes[len(hashes)-1] = parent.Hash() 66 blockm := make(map[common.Hash]*types.Block, n+1) 67 blockm[parent.Hash()] = parent 68 for i, b := range blocks { 69 hashes[len(hashes)-i-2] = b.Hash() 70 blockm[b.Hash()] = b 71 } 72 return hashes, blockm 73 } 74 75 // fetcherTester is a test simulator for mocking out local block chain. 76 type fetcherTester struct { 77 fetcher *Fetcher 78 79 hashes []common.Hash // Hash chain belonging to the tester 80 blocks map[common.Hash]*types.Block // Blocks belonging to the tester 81 drops map[string]bool // Map of peers dropped by the fetcher 82 83 lock sync.RWMutex 84 } 85 86 // newTester creates a new fetcher test mocker. 87 func newTester() *fetcherTester { 88 tester := &fetcherTester{ 89 hashes: []common.Hash{genesis.Hash()}, 90 blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, 91 drops: make(map[string]bool), 92 } 93 tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertChain, tester.dropPeer) 94 tester.fetcher.Start() 95 96 return tester 97 } 98 99 // getBlock retrieves a block from the tester's block chain. 100 func (f *fetcherTester) getBlock(ctx context.Context, hash common.Hash) *types.Block { 101 f.lock.RLock() 102 defer f.lock.RUnlock() 103 104 return f.blocks[hash] 105 } 106 107 // verifyHeader is a nop placeholder for the block header verification. 108 func (f *fetcherTester) verifyHeader(ctx context.Context, header *types.Header) error { 109 return nil 110 } 111 112 // broadcastBlock is a nop placeholder for the block broadcasting. 113 func (f *fetcherTester) broadcastBlock(ctx context.Context, block *types.Block, propagate bool) { 114 } 115 116 // chainHeight retrieves the current height (block number) of the chain. 117 func (f *fetcherTester) chainHeight() uint64 { 118 f.lock.RLock() 119 defer f.lock.RUnlock() 120 121 return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() 122 } 123 124 // insertChain injects a new blocks into the simulated chain. 125 func (f *fetcherTester) insertChain(ctx context.Context, blocks types.Blocks) (int, error) { 126 f.lock.Lock() 127 defer f.lock.Unlock() 128 129 for i, block := range blocks { 130 // Make sure the parent in known 131 if _, ok := f.blocks[block.ParentHash()]; !ok { 132 return i, errors.New("unknown parent") 133 } 134 // Discard any new blocks if the same height already exists 135 if block.NumberU64() <= f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() { 136 return i, nil 137 } 138 // Otherwise build our current chain 139 f.hashes = append(f.hashes, block.Hash()) 140 f.blocks[block.Hash()] = block 141 } 142 return 0, nil 143 } 144 145 // dropPeer is an emulator for the peer removal, simply accumulating the various 146 // peers dropped by the fetcher. 147 func (f *fetcherTester) dropPeer(peer string) { 148 f.lock.Lock() 149 defer f.lock.Unlock() 150 151 f.drops[peer] = true 152 } 153 154 // makeHeaderFetcher retrieves a block header fetcher associated with a simulated peer. 155 func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn { 156 closure := make(map[common.Hash]*types.Block) 157 for hash, block := range blocks { 158 closure[hash] = block 159 } 160 // Create a function that return a header from the closure 161 return func(ctx context.Context, hash common.Hash) error { 162 // Gather the blocks to return 163 headers := make([]*types.Header, 0, 1) 164 if block, ok := closure[hash]; ok { 165 headers = append(headers, block.Header()) 166 } 167 // Return on a new thread 168 go f.fetcher.FilterHeaders(peer, headers, time.Now().Add(drift)) 169 170 return nil 171 } 172 } 173 174 // makeBodyFetcher retrieves a block body fetcher associated with a simulated peer. 175 func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) bodyRequesterFn { 176 closure := make(map[common.Hash]*types.Block) 177 for hash, block := range blocks { 178 closure[hash] = block 179 } 180 // Create a function that returns blocks from the closure 181 return func(ctx context.Context, hashes []common.Hash) error { 182 // Gather the block bodies to return 183 transactions := make([][]*types.Transaction, 0, len(hashes)) 184 185 for _, hash := range hashes { 186 if block, ok := closure[hash]; ok { 187 transactions = append(transactions, block.Transactions()) 188 } 189 } 190 // Return on a new thread 191 go f.fetcher.FilterBodies(peer, transactions, time.Now().Add(drift)) 192 193 return nil 194 } 195 } 196 197 // verifyFetchingEvent verifies that one single event arrive on an fetching channel. 198 func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) { 199 if arrive { 200 select { 201 case <-fetching: 202 case <-time.After(time.Second): 203 t.Fatalf("fetching timeout") 204 } 205 } else { 206 select { 207 case <-fetching: 208 t.Fatalf("fetching invoked") 209 case <-time.After(10 * time.Millisecond): 210 } 211 } 212 } 213 214 // verifyCompletingEvent verifies that one single event arrive on an completing channel. 215 func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive bool) { 216 if arrive { 217 select { 218 case <-completing: 219 case <-time.After(time.Second): 220 t.Fatalf("completing timeout") 221 } 222 } else { 223 select { 224 case <-completing: 225 t.Fatalf("completing invoked") 226 case <-time.After(10 * time.Millisecond): 227 } 228 } 229 } 230 231 // verifyImportEvent verifies that one single event arrive on an import channel. 232 func verifyImportEvent(t *testing.T, imported chan *types.Block, arrive bool) { 233 if arrive { 234 select { 235 case <-imported: 236 case <-time.After(time.Second): 237 t.Fatalf("import timeout") 238 } 239 } else { 240 select { 241 case <-imported: 242 t.Fatalf("import invoked") 243 case <-time.After(10 * time.Millisecond): 244 } 245 } 246 } 247 248 // verifyImportCount verifies that exactly count number of events arrive on an 249 // import hook channel. 250 func verifyImportCount(t *testing.T, imported chan *types.Block, count int) { 251 for i := 0; i < count; i++ { 252 select { 253 case <-imported: 254 case <-time.After(time.Second): 255 t.Fatalf("block %d: import timeout", i+1) 256 } 257 } 258 verifyImportDone(t, imported) 259 } 260 261 // verifyImportDone verifies that no more events are arriving on an import channel. 262 func verifyImportDone(t *testing.T, imported chan *types.Block) { 263 select { 264 case <-imported: 265 t.Fatalf("extra block imported") 266 case <-time.After(50 * time.Millisecond): 267 } 268 } 269 270 // Tests that a fetcher accepts block announcements and initiates retrievals for 271 // them, successfully importing into the local chain. 272 func TestSequentialAnnouncements(t *testing.T) { 273 // Create a chain of blocks to import 274 targetBlocks := 4 * hashLimit 275 hashes, blocks := makeChain(targetBlocks, 0, genesis) 276 277 tester := newTester() 278 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 279 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 280 281 // Iteratively announce blocks until all are imported 282 imported := make(chan *types.Block) 283 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 284 285 for i := len(hashes) - 2; i >= 0; i-- { 286 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 287 verifyImportEvent(t, imported, true) 288 } 289 verifyImportDone(t, imported) 290 } 291 292 // Tests that if blocks are announced by multiple peers (or even the same buggy 293 // peer), they will only get downloaded at most once. 294 func TestConcurrentAnnouncements(t *testing.T) { 295 // Create a chain of blocks to import 296 targetBlocks := 4 * hashLimit 297 hashes, blocks := makeChain(targetBlocks, 0, genesis) 298 299 // Assemble a tester with a built in counter for the requests 300 tester := newTester() 301 firstHeaderFetcher := tester.makeHeaderFetcher("first", blocks, -gatherSlack) 302 firstBodyFetcher := tester.makeBodyFetcher("first", blocks, 0) 303 secondHeaderFetcher := tester.makeHeaderFetcher("second", blocks, -gatherSlack) 304 secondBodyFetcher := tester.makeBodyFetcher("second", blocks, 0) 305 306 counter := uint32(0) 307 firstHeaderWrapper := func(ctx context.Context, hash common.Hash) error { 308 atomic.AddUint32(&counter, 1) 309 return firstHeaderFetcher(ctx, hash) 310 } 311 secondHeaderWrapper := func(ctx context.Context, hash common.Hash) error { 312 atomic.AddUint32(&counter, 1) 313 return secondHeaderFetcher(ctx, hash) 314 } 315 // Iteratively announce blocks until all are imported 316 imported := make(chan *types.Block) 317 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 318 319 for i := len(hashes) - 2; i >= 0; i-- { 320 tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher) 321 tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher) 322 tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), secondHeaderWrapper, secondBodyFetcher) 323 verifyImportEvent(t, imported, true) 324 } 325 verifyImportDone(t, imported) 326 327 // Make sure no blocks were retrieved twice 328 if int(counter) != targetBlocks { 329 t.Fatalf("retrieval count mismatch: have %v, want %v", counter, targetBlocks) 330 } 331 } 332 333 // Tests that announcements arriving while a previous is being fetched still 334 // results in a valid import. 335 func TestOverlappingAnnouncements(t *testing.T) { 336 // Create a chain of blocks to import 337 targetBlocks := 4 * hashLimit 338 hashes, blocks := makeChain(targetBlocks, 0, genesis) 339 340 tester := newTester() 341 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 342 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 343 344 // Iteratively announce blocks, but overlap them continuously 345 overlap := 16 346 imported := make(chan *types.Block, len(hashes)-1) 347 for i := 0; i < overlap; i++ { 348 imported <- nil 349 } 350 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 351 352 for i := len(hashes) - 2; i >= 0; i-- { 353 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 354 select { 355 case <-imported: 356 case <-time.After(time.Second): 357 t.Fatalf("block %d: import timeout", len(hashes)-i) 358 } 359 } 360 // Wait for all the imports to complete and check count 361 verifyImportCount(t, imported, overlap) 362 } 363 364 // Tests that announces already being retrieved will not be duplicated. 365 func TestPendingDeduplication(t *testing.T) { 366 // Create a hash and corresponding block 367 hashes, blocks := makeChain(1, 0, genesis) 368 369 // Assemble a tester with a built in counter and delayed fetcher 370 tester := newTester() 371 headerFetcher := tester.makeHeaderFetcher("repeater", blocks, -gatherSlack) 372 bodyFetcher := tester.makeBodyFetcher("repeater", blocks, 0) 373 374 delay := 50 * time.Millisecond 375 counter := uint32(0) 376 headerWrapper := func(ctx context.Context, hash common.Hash) error { 377 atomic.AddUint32(&counter, 1) 378 379 // Simulate a long running fetch 380 go func() { 381 time.Sleep(delay) 382 headerFetcher(ctx, hash) 383 }() 384 return nil 385 } 386 // Announce the same block many times until it's fetched (wait for any pending ops) 387 for tester.getBlock(context.Background(), hashes[0]) == nil { 388 tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher) 389 time.Sleep(time.Millisecond) 390 } 391 time.Sleep(delay) 392 393 // Check that all blocks were imported and none fetched twice 394 if imported := len(tester.blocks); imported != 2 { 395 t.Fatalf("synchronised block mismatch: have %v, want %v", imported, 2) 396 } 397 if int(counter) != 1 { 398 t.Fatalf("retrieval count mismatch: have %v, want %v", counter, 1) 399 } 400 } 401 402 // Tests that announcements retrieved in a random order are cached and eventually 403 // imported when all the gaps are filled in. 404 func TestRandomArrivalImport(t *testing.T) { 405 // Create a chain of blocks to import, and choose one to delay 406 targetBlocks := maxQueueDist 407 hashes, blocks := makeChain(targetBlocks, 0, genesis) 408 skip := targetBlocks / 2 409 410 tester := newTester() 411 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 412 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 413 414 // Iteratively announce blocks, skipping one entry 415 imported := make(chan *types.Block, len(hashes)-1) 416 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 417 418 for i := len(hashes) - 1; i >= 0; i-- { 419 if i != skip { 420 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 421 time.Sleep(time.Millisecond) 422 } 423 } 424 // Finally announce the skipped entry and check full import 425 tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 426 verifyImportCount(t, imported, len(hashes)-1) 427 } 428 429 // Tests that direct block enqueues (due to block propagation vs. hash announce) 430 // are correctly schedule, filling and import queue gaps. 431 func TestQueueGapFill(t *testing.T) { 432 // Create a chain of blocks to import, and choose one to not announce at all 433 targetBlocks := maxQueueDist 434 hashes, blocks := makeChain(targetBlocks, 0, genesis) 435 skip := targetBlocks / 2 436 437 tester := newTester() 438 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 439 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 440 441 // Iteratively announce blocks, skipping one entry 442 imported := make(chan *types.Block, len(hashes)-1) 443 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 444 445 for i := len(hashes) - 1; i >= 0; i-- { 446 if i != skip { 447 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 448 time.Sleep(time.Millisecond) 449 } 450 } 451 // Fill the missing block directly as if propagated 452 tester.fetcher.Enqueue("valid", blocks[hashes[skip]]) 453 verifyImportCount(t, imported, len(hashes)-1) 454 } 455 456 // Tests that blocks arriving from various sources (multiple propagations, hash 457 // announces, etc) do not get scheduled for import multiple times. 458 func TestImportDeduplication(t *testing.T) { 459 // Create two blocks to import (one for duplication, the other for stalling) 460 hashes, blocks := makeChain(2, 0, genesis) 461 462 // Create the tester and wrap the importer with a counter 463 tester := newTester() 464 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 465 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 466 467 counter := uint32(0) 468 tester.fetcher.insertChain = func(ctx context.Context, blocks types.Blocks) (int, error) { 469 atomic.AddUint32(&counter, uint32(len(blocks))) 470 return tester.insertChain(ctx, blocks) 471 } 472 // Instrument the fetching and imported events 473 fetching := make(chan []common.Hash) 474 imported := make(chan *types.Block, len(hashes)-1) 475 tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes } 476 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 477 478 // Announce the duplicating block, wait for retrieval, and also propagate directly 479 tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 480 <-fetching 481 482 tester.fetcher.Enqueue("valid", blocks[hashes[0]]) 483 tester.fetcher.Enqueue("valid", blocks[hashes[0]]) 484 tester.fetcher.Enqueue("valid", blocks[hashes[0]]) 485 486 // Fill the missing block directly as if propagated, and check import uniqueness 487 tester.fetcher.Enqueue("valid", blocks[hashes[1]]) 488 verifyImportCount(t, imported, 2) 489 490 if counter != 2 { 491 t.Fatalf("import invocation count mismatch: have %v, want %v", counter, 2) 492 } 493 } 494 495 // Tests that blocks with numbers much lower or higher than out current head get 496 // discarded to prevent wasting resources on useless blocks from faulty peers. 497 func TestDistantPropagationDiscarding(t *testing.T) { 498 // Create a long chain to import and define the discard boundaries 499 hashes, blocks := makeChain(3*maxQueueDist, 0, genesis) 500 head := hashes[len(hashes)/2] 501 502 low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1 503 504 // Create a tester and simulate a head block being the middle of the above chain 505 tester := newTester() 506 507 tester.lock.Lock() 508 tester.hashes = []common.Hash{head} 509 tester.blocks = map[common.Hash]*types.Block{head: blocks[head]} 510 tester.lock.Unlock() 511 512 // Ensure that a block with a lower number than the threshold is discarded 513 tester.fetcher.Enqueue("lower", blocks[hashes[low]]) 514 time.Sleep(10 * time.Millisecond) 515 if !tester.fetcher.queue.Empty() { 516 t.Fatalf("fetcher queued stale block") 517 } 518 // Ensure that a block with a higher number than the threshold is discarded 519 tester.fetcher.Enqueue("higher", blocks[hashes[high]]) 520 time.Sleep(10 * time.Millisecond) 521 if !tester.fetcher.queue.Empty() { 522 t.Fatalf("fetcher queued future block") 523 } 524 } 525 526 // Tests that announcements with numbers much lower or higher than out current 527 // head get discarded to prevent wasting resources on useless blocks from faulty 528 // peers. 529 func TestDistantAnnouncementDiscarding(t *testing.T) { 530 // Create a long chain to import and define the discard boundaries 531 hashes, blocks := makeChain(3*maxQueueDist, 0, genesis) 532 head := hashes[len(hashes)/2] 533 534 low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1 535 536 // Create a tester and simulate a head block being the middle of the above chain 537 tester := newTester() 538 539 tester.lock.Lock() 540 tester.hashes = []common.Hash{head} 541 tester.blocks = map[common.Hash]*types.Block{head: blocks[head]} 542 tester.lock.Unlock() 543 544 headerFetcher := tester.makeHeaderFetcher("lower", blocks, -gatherSlack) 545 bodyFetcher := tester.makeBodyFetcher("lower", blocks, 0) 546 547 fetching := make(chan struct{}, 2) 548 tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} } 549 550 // Ensure that a block with a lower number than the threshold is discarded 551 tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 552 select { 553 case <-time.After(50 * time.Millisecond): 554 case <-fetching: 555 t.Fatalf("fetcher requested stale header") 556 } 557 // Ensure that a block with a higher number than the threshold is discarded 558 tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 559 select { 560 case <-time.After(50 * time.Millisecond): 561 case <-fetching: 562 t.Fatalf("fetcher requested future header") 563 } 564 } 565 566 // Tests that peers announcing blocks with invalid numbers (i.e. not matching 567 // the headers provided afterwards) get dropped as malicious. 568 func TestInvalidNumberAnnouncement(t *testing.T) { 569 // Create a single block to import and check numbers against 570 hashes, blocks := makeChain(1, 0, genesis) 571 572 tester := newTester() 573 badHeaderFetcher := tester.makeHeaderFetcher("bad", blocks, -gatherSlack) 574 badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0) 575 576 imported := make(chan *types.Block) 577 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 578 579 // Announce a block with a bad number, check for immediate drop 580 tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher) 581 verifyImportEvent(t, imported, false) 582 583 tester.lock.RLock() 584 dropped := tester.drops["bad"] 585 tester.lock.RUnlock() 586 587 if !dropped { 588 t.Fatalf("peer with invalid numbered announcement not dropped") 589 } 590 591 goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack) 592 goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0) 593 // Make sure a good announcement passes without a drop 594 tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher) 595 verifyImportEvent(t, imported, true) 596 597 tester.lock.RLock() 598 dropped = tester.drops["good"] 599 tester.lock.RUnlock() 600 601 if dropped { 602 t.Fatalf("peer with valid numbered announcement dropped") 603 } 604 verifyImportDone(t, imported) 605 } 606 607 // Tests that if a block is empty (i.e. header only), no body request should be 608 // made, and instead the header should be assembled into a whole block in itself. 609 func TestEmptyBlockShortCircuit(t *testing.T) { 610 // Create a chain of blocks to import 611 hashes, blocks := makeChain(32, 0, genesis) 612 613 tester := newTester() 614 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 615 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 616 617 // Add a monitoring hook for all internal events 618 fetching := make(chan []common.Hash) 619 tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes } 620 621 completing := make(chan []common.Hash) 622 tester.fetcher.completingHook = func(hashes []common.Hash) { completing <- hashes } 623 624 imported := make(chan *types.Block) 625 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 626 627 // Iteratively announce blocks until all are imported 628 for i := len(hashes) - 2; i >= 0; i-- { 629 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 630 631 // All announces should fetch the header 632 verifyFetchingEvent(t, fetching, true) 633 634 // Only blocks with data contents should request bodies 635 verifyCompletingEvent(t, completing, len(blocks[hashes[i]].Transactions()) > 0 || len(blocks[hashes[i]].Uncles()) > 0) 636 637 // Irrelevant of the construct, import should succeed 638 verifyImportEvent(t, imported, true) 639 } 640 verifyImportDone(t, imported) 641 } 642 643 // Tests that a peer is unable to use unbounded memory with sending infinite 644 // block announcements to a node, but that even in the face of such an attack, 645 // the fetcher remains operational. 646 func TestHashMemoryExhaustionAttack(t *testing.T) { 647 // Create a tester with instrumented import hooks 648 tester := newTester() 649 650 imported, announces := make(chan *types.Block), int32(0) 651 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 652 tester.fetcher.announceChangeHook = func(hash common.Hash, added bool) { 653 if added { 654 atomic.AddInt32(&announces, 1) 655 } else { 656 atomic.AddInt32(&announces, -1) 657 } 658 } 659 // Create a valid chain and an infinite junk chain 660 targetBlocks := hashLimit + 2*maxQueueDist 661 hashes, blocks := makeChain(targetBlocks, 0, genesis) 662 validHeaderFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 663 validBodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 664 665 attack, _ := makeChain(targetBlocks, 0, unknownBlock) 666 attackerHeaderFetcher := tester.makeHeaderFetcher("attacker", nil, -gatherSlack) 667 attackerBodyFetcher := tester.makeBodyFetcher("attacker", nil, 0) 668 669 // Feed the tester a huge hashset from the attacker, and a limited from the valid peer 670 for i := 0; i < len(attack); i++ { 671 if i < maxQueueDist { 672 tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher) 673 } 674 tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher) 675 } 676 if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist { 677 t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) 678 } 679 // Wait for fetches to complete 680 verifyImportCount(t, imported, maxQueueDist) 681 682 // Feed the remaining valid hashes to ensure DOS protection state remains clean 683 for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- { 684 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher) 685 verifyImportEvent(t, imported, true) 686 } 687 verifyImportDone(t, imported) 688 } 689 690 // Tests that blocks sent to the fetcher (either through propagation or via hash 691 // announces and retrievals) don't pile up indefinitely, exhausting available 692 // system memory. 693 func TestBlockMemoryExhaustionAttack(t *testing.T) { 694 // Create a tester with instrumented import hooks 695 tester := newTester() 696 697 imported, enqueued := make(chan *types.Block), int32(0) 698 tester.fetcher.importedHook = func(block *types.Block) { imported <- block } 699 tester.fetcher.queueChangeHook = func(hash common.Hash, added bool) { 700 if added { 701 atomic.AddInt32(&enqueued, 1) 702 } else { 703 atomic.AddInt32(&enqueued, -1) 704 } 705 } 706 // Create a valid chain and a batch of dangling (but in range) blocks 707 targetBlocks := hashLimit + 2*maxQueueDist 708 hashes, blocks := makeChain(targetBlocks, 0, genesis) 709 attack := make(map[common.Hash]*types.Block) 710 for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ { 711 hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock) 712 for _, hash := range hashes[:maxQueueDist-2] { 713 attack[hash] = blocks[hash] 714 } 715 } 716 // Try to feed all the attacker blocks make sure only a limited batch is accepted 717 for _, block := range attack { 718 tester.fetcher.Enqueue("attacker", block) 719 } 720 time.Sleep(200 * time.Millisecond) 721 if queued := atomic.LoadInt32(&enqueued); queued != blockLimit { 722 t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit) 723 } 724 // Queue up a batch of valid blocks, and check that a new peer is allowed to do so 725 for i := 0; i < maxQueueDist-1; i++ { 726 tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]]) 727 } 728 time.Sleep(100 * time.Millisecond) 729 if queued := atomic.LoadInt32(&enqueued); queued != blockLimit+maxQueueDist-1 { 730 t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1) 731 } 732 // Insert the missing piece (and sanity check the import) 733 tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2]]) 734 verifyImportCount(t, imported, maxQueueDist) 735 736 // Insert the remaining blocks in chunks to ensure clean DOS protection 737 for i := maxQueueDist; i < len(hashes)-1; i++ { 738 tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2-i]]) 739 verifyImportEvent(t, imported, true) 740 } 741 verifyImportDone(t, imported) 742 }