github.com/theQRL/go-zond@v0.1.1/zond/fetcher/block_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 "errors" 21 "math/big" 22 "sync" 23 "sync/atomic" 24 "testing" 25 "time" 26 27 "github.com/theQRL/go-zond/common" 28 "github.com/theQRL/go-zond/consensus/ethash" 29 "github.com/theQRL/go-zond/core" 30 "github.com/theQRL/go-zond/core/rawdb" 31 "github.com/theQRL/go-zond/core/types" 32 "github.com/theQRL/go-zond/crypto" 33 "github.com/theQRL/go-zond/zond/protocols/zond" 34 "github.com/theQRL/go-zond/params" 35 "github.com/theQRL/go-zond/trie" 36 ) 37 38 var ( 39 testdb = rawdb.NewMemoryDatabase() 40 testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 41 testAddress = crypto.PubkeyToAddress(testKey.PublicKey) 42 gspec = &core.Genesis{ 43 Config: params.TestChainConfig, 44 Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, 45 BaseFee: big.NewInt(params.InitialBaseFee), 46 } 47 genesis = gspec.MustCommit(testdb, trie.NewDatabase(testdb, trie.HashDefaults)) 48 unknownBlock = types.NewBlock(&types.Header{Root: types.EmptyRootHash, GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) 49 ) 50 51 // makeChain creates a chain of n blocks starting at and including parent. 52 // the returned hash chain is ordered head->parent. In addition, every 3rd block 53 // contains a transaction and every 5th an uncle to allow testing correct block 54 // reassembly. 55 func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { 56 blocks, _ := core.GenerateChain(gspec.Config, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) { 57 block.SetCoinbase(common.Address{seed}) 58 59 // If the block number is multiple of 3, send a bonus transaction to the miner 60 if parent == genesis && i%3 == 0 { 61 signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp()) 62 tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) 63 if err != nil { 64 panic(err) 65 } 66 block.AddTx(tx) 67 } 68 // If the block number is a multiple of 5, add a bonus uncle to the block 69 if i > 0 && i%5 == 0 { 70 block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))}) 71 } 72 }) 73 hashes := make([]common.Hash, n+1) 74 hashes[len(hashes)-1] = parent.Hash() 75 blockm := make(map[common.Hash]*types.Block, n+1) 76 blockm[parent.Hash()] = parent 77 for i, b := range blocks { 78 hashes[len(hashes)-i-2] = b.Hash() 79 blockm[b.Hash()] = b 80 } 81 return hashes, blockm 82 } 83 84 // fetcherTester is a test simulator for mocking out local block chain. 85 type fetcherTester struct { 86 fetcher *BlockFetcher 87 88 hashes []common.Hash // Hash chain belonging to the tester 89 headers map[common.Hash]*types.Header // Headers belonging to the tester 90 blocks map[common.Hash]*types.Block // Blocks belonging to the tester 91 drops map[string]bool // Map of peers dropped by the fetcher 92 93 lock sync.RWMutex 94 } 95 96 // newTester creates a new fetcher test mocker. 97 func newTester(light bool) *fetcherTester { 98 tester := &fetcherTester{ 99 hashes: []common.Hash{genesis.Hash()}, 100 headers: map[common.Hash]*types.Header{genesis.Hash(): genesis.Header()}, 101 blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, 102 drops: make(map[string]bool), 103 } 104 tester.fetcher = NewBlockFetcher(light, tester.getHeader, tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertHeaders, tester.insertChain, tester.dropPeer) 105 tester.fetcher.Start() 106 107 return tester 108 } 109 110 // getHeader retrieves a header from the tester's block chain. 111 func (f *fetcherTester) getHeader(hash common.Hash) *types.Header { 112 f.lock.RLock() 113 defer f.lock.RUnlock() 114 115 return f.headers[hash] 116 } 117 118 // getBlock retrieves a block from the tester's block chain. 119 func (f *fetcherTester) getBlock(hash common.Hash) *types.Block { 120 f.lock.RLock() 121 defer f.lock.RUnlock() 122 123 return f.blocks[hash] 124 } 125 126 // verifyHeader is a nop placeholder for the block header verification. 127 func (f *fetcherTester) verifyHeader(header *types.Header) error { 128 return nil 129 } 130 131 // broadcastBlock is a nop placeholder for the block broadcasting. 132 func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) { 133 } 134 135 // chainHeight retrieves the current height (block number) of the chain. 136 func (f *fetcherTester) chainHeight() uint64 { 137 f.lock.RLock() 138 defer f.lock.RUnlock() 139 140 if f.fetcher.light { 141 return f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() 142 } 143 return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() 144 } 145 146 // insertChain injects a new headers into the simulated chain. 147 func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) { 148 f.lock.Lock() 149 defer f.lock.Unlock() 150 151 for i, header := range headers { 152 // Make sure the parent in known 153 if _, ok := f.headers[header.ParentHash]; !ok { 154 return i, errors.New("unknown parent") 155 } 156 // Discard any new blocks if the same height already exists 157 if header.Number.Uint64() <= f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() { 158 return i, nil 159 } 160 // Otherwise build our current chain 161 f.hashes = append(f.hashes, header.Hash()) 162 f.headers[header.Hash()] = header 163 } 164 return 0, nil 165 } 166 167 // insertChain injects a new blocks into the simulated chain. 168 func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) { 169 f.lock.Lock() 170 defer f.lock.Unlock() 171 172 for i, block := range blocks { 173 // Make sure the parent in known 174 if _, ok := f.blocks[block.ParentHash()]; !ok { 175 return i, errors.New("unknown parent") 176 } 177 // Discard any new blocks if the same height already exists 178 if block.NumberU64() <= f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() { 179 return i, nil 180 } 181 // Otherwise build our current chain 182 f.hashes = append(f.hashes, block.Hash()) 183 f.blocks[block.Hash()] = block 184 } 185 return 0, nil 186 } 187 188 // dropPeer is an emulator for the peer removal, simply accumulating the various 189 // peers dropped by the fetcher. 190 func (f *fetcherTester) dropPeer(peer string) { 191 f.lock.Lock() 192 defer f.lock.Unlock() 193 194 f.drops[peer] = true 195 } 196 197 // makeHeaderFetcher retrieves a block header fetcher associated with a simulated peer. 198 func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn { 199 closure := make(map[common.Hash]*types.Block) 200 for hash, block := range blocks { 201 closure[hash] = block 202 } 203 // Create a function that return a header from the closure 204 return func(hash common.Hash, sink chan *zond.Response) (*zond.Request, error) { 205 // Gather the blocks to return 206 headers := make([]*types.Header, 0, 1) 207 if block, ok := closure[hash]; ok { 208 headers = append(headers, block.Header()) 209 } 210 // Return on a new thread 211 req := &zond.Request{ 212 Peer: peer, 213 } 214 res := &zond.Response{ 215 Req: req, 216 Res: (*zond.BlockHeadersPacket)(&headers), 217 Time: drift, 218 Done: make(chan error, 1), // Ignore the returned status 219 } 220 go func() { 221 sink <- res 222 }() 223 return req, nil 224 } 225 } 226 227 // makeBodyFetcher retrieves a block body fetcher associated with a simulated peer. 228 func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) bodyRequesterFn { 229 closure := make(map[common.Hash]*types.Block) 230 for hash, block := range blocks { 231 closure[hash] = block 232 } 233 // Create a function that returns blocks from the closure 234 return func(hashes []common.Hash, sink chan *zond.Response) (*zond.Request, error) { 235 // Gather the block bodies to return 236 transactions := make([][]*types.Transaction, 0, len(hashes)) 237 uncles := make([][]*types.Header, 0, len(hashes)) 238 239 for _, hash := range hashes { 240 if block, ok := closure[hash]; ok { 241 transactions = append(transactions, block.Transactions()) 242 uncles = append(uncles, block.Uncles()) 243 } 244 } 245 // Return on a new thread 246 bodies := make([]*zond.BlockBody, len(transactions)) 247 for i, txs := range transactions { 248 bodies[i] = &zond.BlockBody{ 249 Transactions: txs, 250 Uncles: uncles[i], 251 } 252 } 253 req := &zond.Request{ 254 Peer: peer, 255 } 256 res := &zond.Response{ 257 Req: req, 258 Res: (*zond.BlockBodiesPacket)(&bodies), 259 Time: drift, 260 Done: make(chan error, 1), // Ignore the returned status 261 } 262 go func() { 263 sink <- res 264 }() 265 return req, nil 266 } 267 } 268 269 // verifyFetchingEvent verifies that one single event arrive on a fetching channel. 270 func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) { 271 t.Helper() 272 273 if arrive { 274 select { 275 case <-fetching: 276 case <-time.After(time.Second): 277 t.Fatalf("fetching timeout") 278 } 279 } else { 280 select { 281 case <-fetching: 282 t.Fatalf("fetching invoked") 283 case <-time.After(10 * time.Millisecond): 284 } 285 } 286 } 287 288 // verifyCompletingEvent verifies that one single event arrive on an completing channel. 289 func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive bool) { 290 t.Helper() 291 292 if arrive { 293 select { 294 case <-completing: 295 case <-time.After(time.Second): 296 t.Fatalf("completing timeout") 297 } 298 } else { 299 select { 300 case <-completing: 301 t.Fatalf("completing invoked") 302 case <-time.After(10 * time.Millisecond): 303 } 304 } 305 } 306 307 // verifyImportEvent verifies that one single event arrive on an import channel. 308 func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) { 309 t.Helper() 310 311 if arrive { 312 select { 313 case <-imported: 314 case <-time.After(time.Second): 315 t.Fatalf("import timeout") 316 } 317 } else { 318 select { 319 case <-imported: 320 t.Fatalf("import invoked") 321 case <-time.After(20 * time.Millisecond): 322 } 323 } 324 } 325 326 // verifyImportCount verifies that exactly count number of events arrive on an 327 // import hook channel. 328 func verifyImportCount(t *testing.T, imported chan interface{}, count int) { 329 t.Helper() 330 331 for i := 0; i < count; i++ { 332 select { 333 case <-imported: 334 case <-time.After(time.Second): 335 t.Fatalf("block %d: import timeout", i+1) 336 } 337 } 338 verifyImportDone(t, imported) 339 } 340 341 // verifyImportDone verifies that no more events are arriving on an import channel. 342 func verifyImportDone(t *testing.T, imported chan interface{}) { 343 t.Helper() 344 345 select { 346 case <-imported: 347 t.Fatalf("extra block imported") 348 case <-time.After(50 * time.Millisecond): 349 } 350 } 351 352 // verifyChainHeight verifies the chain height is as expected. 353 func verifyChainHeight(t *testing.T, fetcher *fetcherTester, height uint64) { 354 t.Helper() 355 356 if fetcher.chainHeight() != height { 357 t.Fatalf("chain height mismatch, got %d, want %d", fetcher.chainHeight(), height) 358 } 359 } 360 361 // Tests that a fetcher accepts block/header announcements and initiates retrievals 362 // for them, successfully importing into the local chain. 363 func TestFullSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, false) } 364 func TestLightSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, true) } 365 366 func testSequentialAnnouncements(t *testing.T, light bool) { 367 // Create a chain of blocks to import 368 targetBlocks := 4 * hashLimit 369 hashes, blocks := makeChain(targetBlocks, 0, genesis) 370 371 tester := newTester(light) 372 defer tester.fetcher.Stop() 373 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 374 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 375 376 // Iteratively announce blocks until all are imported 377 imported := make(chan interface{}) 378 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { 379 if light { 380 if header == nil { 381 t.Fatalf("Fetcher try to import empty header") 382 } 383 imported <- header 384 } else { 385 if block == nil { 386 t.Fatalf("Fetcher try to import empty block") 387 } 388 imported <- block 389 } 390 } 391 for i := len(hashes) - 2; i >= 0; i-- { 392 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 393 verifyImportEvent(t, imported, true) 394 } 395 verifyImportDone(t, imported) 396 verifyChainHeight(t, tester, uint64(len(hashes)-1)) 397 } 398 399 // Tests that if blocks are announced by multiple peers (or even the same buggy 400 // peer), they will only get downloaded at most once. 401 func TestFullConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, false) } 402 func TestLightConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, true) } 403 404 func testConcurrentAnnouncements(t *testing.T, light bool) { 405 // Create a chain of blocks to import 406 targetBlocks := 4 * hashLimit 407 hashes, blocks := makeChain(targetBlocks, 0, genesis) 408 409 // Assemble a tester with a built in counter for the requests 410 tester := newTester(light) 411 firstHeaderFetcher := tester.makeHeaderFetcher("first", blocks, -gatherSlack) 412 firstBodyFetcher := tester.makeBodyFetcher("first", blocks, 0) 413 secondHeaderFetcher := tester.makeHeaderFetcher("second", blocks, -gatherSlack) 414 secondBodyFetcher := tester.makeBodyFetcher("second", blocks, 0) 415 416 var counter atomic.Uint32 417 firstHeaderWrapper := func(hash common.Hash, sink chan *zond.Response) (*zond.Request, error) { 418 counter.Add(1) 419 return firstHeaderFetcher(hash, sink) 420 } 421 secondHeaderWrapper := func(hash common.Hash, sink chan *zond.Response) (*zond.Request, error) { 422 counter.Add(1) 423 return secondHeaderFetcher(hash, sink) 424 } 425 // Iteratively announce blocks until all are imported 426 imported := make(chan interface{}) 427 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { 428 if light { 429 if header == nil { 430 t.Fatalf("Fetcher try to import empty header") 431 } 432 imported <- header 433 } else { 434 if block == nil { 435 t.Fatalf("Fetcher try to import empty block") 436 } 437 imported <- block 438 } 439 } 440 for i := len(hashes) - 2; i >= 0; i-- { 441 tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher) 442 tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher) 443 tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), secondHeaderWrapper, secondBodyFetcher) 444 verifyImportEvent(t, imported, true) 445 } 446 verifyImportDone(t, imported) 447 448 // Make sure no blocks were retrieved twice 449 if c := int(counter.Load()); c != targetBlocks { 450 t.Fatalf("retrieval count mismatch: have %v, want %v", c, targetBlocks) 451 } 452 verifyChainHeight(t, tester, uint64(len(hashes)-1)) 453 } 454 455 // Tests that announcements arriving while a previous is being fetched still 456 // results in a valid import. 457 func TestFullOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, false) } 458 func TestLightOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, true) } 459 460 func testOverlappingAnnouncements(t *testing.T, light bool) { 461 // Create a chain of blocks to import 462 targetBlocks := 4 * hashLimit 463 hashes, blocks := makeChain(targetBlocks, 0, genesis) 464 465 tester := newTester(light) 466 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 467 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 468 469 // Iteratively announce blocks, but overlap them continuously 470 overlap := 16 471 imported := make(chan interface{}, len(hashes)-1) 472 for i := 0; i < overlap; i++ { 473 imported <- nil 474 } 475 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { 476 if light { 477 if header == nil { 478 t.Fatalf("Fetcher try to import empty header") 479 } 480 imported <- header 481 } else { 482 if block == nil { 483 t.Fatalf("Fetcher try to import empty block") 484 } 485 imported <- block 486 } 487 } 488 489 for i := len(hashes) - 2; i >= 0; i-- { 490 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 491 select { 492 case <-imported: 493 case <-time.After(time.Second): 494 t.Fatalf("block %d: import timeout", len(hashes)-i) 495 } 496 } 497 // Wait for all the imports to complete and check count 498 verifyImportCount(t, imported, overlap) 499 verifyChainHeight(t, tester, uint64(len(hashes)-1)) 500 } 501 502 // Tests that announces already being retrieved will not be duplicated. 503 func TestFullPendingDeduplication(t *testing.T) { testPendingDeduplication(t, false) } 504 func TestLightPendingDeduplication(t *testing.T) { testPendingDeduplication(t, true) } 505 506 func testPendingDeduplication(t *testing.T, light bool) { 507 // Create a hash and corresponding block 508 hashes, blocks := makeChain(1, 0, genesis) 509 510 // Assemble a tester with a built in counter and delayed fetcher 511 tester := newTester(light) 512 headerFetcher := tester.makeHeaderFetcher("repeater", blocks, -gatherSlack) 513 bodyFetcher := tester.makeBodyFetcher("repeater", blocks, 0) 514 515 delay := 50 * time.Millisecond 516 var counter atomic.Uint32 517 headerWrapper := func(hash common.Hash, sink chan *zond.Response) (*zond.Request, error) { 518 counter.Add(1) 519 520 // Simulate a long running fetch 521 resink := make(chan *zond.Response) 522 req, err := headerFetcher(hash, resink) 523 if err == nil { 524 go func() { 525 res := <-resink 526 time.Sleep(delay) 527 sink <- res 528 }() 529 } 530 return req, err 531 } 532 checkNonExist := func() bool { 533 return tester.getBlock(hashes[0]) == nil 534 } 535 if light { 536 checkNonExist = func() bool { 537 return tester.getHeader(hashes[0]) == nil 538 } 539 } 540 // Announce the same block many times until it's fetched (wait for any pending ops) 541 for checkNonExist() { 542 tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher) 543 time.Sleep(time.Millisecond) 544 } 545 time.Sleep(delay) 546 547 // Check that all blocks were imported and none fetched twice 548 if c := counter.Load(); c != 1 { 549 t.Fatalf("retrieval count mismatch: have %v, want %v", c, 1) 550 } 551 verifyChainHeight(t, tester, 1) 552 } 553 554 // Tests that announcements retrieved in a random order are cached and eventually 555 // imported when all the gaps are filled in. 556 func TestFullRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, false) } 557 func TestLightRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, true) } 558 559 func testRandomArrivalImport(t *testing.T, light bool) { 560 // Create a chain of blocks to import, and choose one to delay 561 targetBlocks := maxQueueDist 562 hashes, blocks := makeChain(targetBlocks, 0, genesis) 563 skip := targetBlocks / 2 564 565 tester := newTester(light) 566 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 567 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 568 569 // Iteratively announce blocks, skipping one entry 570 imported := make(chan interface{}, len(hashes)-1) 571 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { 572 if light { 573 if header == nil { 574 t.Fatalf("Fetcher try to import empty header") 575 } 576 imported <- header 577 } else { 578 if block == nil { 579 t.Fatalf("Fetcher try to import empty block") 580 } 581 imported <- block 582 } 583 } 584 for i := len(hashes) - 1; i >= 0; i-- { 585 if i != skip { 586 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 587 time.Sleep(time.Millisecond) 588 } 589 } 590 // Finally announce the skipped entry and check full import 591 tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 592 verifyImportCount(t, imported, len(hashes)-1) 593 verifyChainHeight(t, tester, uint64(len(hashes)-1)) 594 } 595 596 // Tests that direct block enqueues (due to block propagation vs. hash announce) 597 // are correctly schedule, filling and import queue gaps. 598 func TestQueueGapFill(t *testing.T) { 599 // Create a chain of blocks to import, and choose one to not announce at all 600 targetBlocks := maxQueueDist 601 hashes, blocks := makeChain(targetBlocks, 0, genesis) 602 skip := targetBlocks / 2 603 604 tester := newTester(false) 605 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 606 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 607 608 // Iteratively announce blocks, skipping one entry 609 imported := make(chan interface{}, len(hashes)-1) 610 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } 611 612 for i := len(hashes) - 1; i >= 0; i-- { 613 if i != skip { 614 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 615 time.Sleep(time.Millisecond) 616 } 617 } 618 // Fill the missing block directly as if propagated 619 tester.fetcher.Enqueue("valid", blocks[hashes[skip]]) 620 verifyImportCount(t, imported, len(hashes)-1) 621 verifyChainHeight(t, tester, uint64(len(hashes)-1)) 622 } 623 624 // Tests that blocks arriving from various sources (multiple propagations, hash 625 // announces, etc) do not get scheduled for import multiple times. 626 func TestImportDeduplication(t *testing.T) { 627 // Create two blocks to import (one for duplication, the other for stalling) 628 hashes, blocks := makeChain(2, 0, genesis) 629 630 // Create the tester and wrap the importer with a counter 631 tester := newTester(false) 632 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 633 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 634 635 var counter atomic.Uint32 636 tester.fetcher.insertChain = func(blocks types.Blocks) (int, error) { 637 counter.Add(uint32(len(blocks))) 638 return tester.insertChain(blocks) 639 } 640 // Instrument the fetching and imported events 641 fetching := make(chan []common.Hash) 642 imported := make(chan interface{}, len(hashes)-1) 643 tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes } 644 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } 645 646 // Announce the duplicating block, wait for retrieval, and also propagate directly 647 tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 648 <-fetching 649 650 tester.fetcher.Enqueue("valid", blocks[hashes[0]]) 651 tester.fetcher.Enqueue("valid", blocks[hashes[0]]) 652 tester.fetcher.Enqueue("valid", blocks[hashes[0]]) 653 654 // Fill the missing block directly as if propagated, and check import uniqueness 655 tester.fetcher.Enqueue("valid", blocks[hashes[1]]) 656 verifyImportCount(t, imported, 2) 657 658 if c := counter.Load(); c != 2 { 659 t.Fatalf("import invocation count mismatch: have %v, want %v", c, 2) 660 } 661 } 662 663 // Tests that blocks with numbers much lower or higher than out current head get 664 // discarded to prevent wasting resources on useless blocks from faulty peers. 665 func TestDistantPropagationDiscarding(t *testing.T) { 666 // Create a long chain to import and define the discard boundaries 667 hashes, blocks := makeChain(3*maxQueueDist, 0, genesis) 668 head := hashes[len(hashes)/2] 669 670 low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1 671 672 // Create a tester and simulate a head block being the middle of the above chain 673 tester := newTester(false) 674 675 tester.lock.Lock() 676 tester.hashes = []common.Hash{head} 677 tester.blocks = map[common.Hash]*types.Block{head: blocks[head]} 678 tester.lock.Unlock() 679 680 // Ensure that a block with a lower number than the threshold is discarded 681 tester.fetcher.Enqueue("lower", blocks[hashes[low]]) 682 time.Sleep(10 * time.Millisecond) 683 if !tester.fetcher.queue.Empty() { 684 t.Fatalf("fetcher queued stale block") 685 } 686 // Ensure that a block with a higher number than the threshold is discarded 687 tester.fetcher.Enqueue("higher", blocks[hashes[high]]) 688 time.Sleep(10 * time.Millisecond) 689 if !tester.fetcher.queue.Empty() { 690 t.Fatalf("fetcher queued future block") 691 } 692 } 693 694 // Tests that announcements with numbers much lower or higher than out current 695 // head get discarded to prevent wasting resources on useless blocks from faulty 696 // peers. 697 func TestFullDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, false) } 698 func TestLightDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, true) } 699 700 func testDistantAnnouncementDiscarding(t *testing.T, light bool) { 701 // Create a long chain to import and define the discard boundaries 702 hashes, blocks := makeChain(3*maxQueueDist, 0, genesis) 703 head := hashes[len(hashes)/2] 704 705 low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1 706 707 // Create a tester and simulate a head block being the middle of the above chain 708 tester := newTester(light) 709 710 tester.lock.Lock() 711 tester.hashes = []common.Hash{head} 712 tester.headers = map[common.Hash]*types.Header{head: blocks[head].Header()} 713 tester.blocks = map[common.Hash]*types.Block{head: blocks[head]} 714 tester.lock.Unlock() 715 716 headerFetcher := tester.makeHeaderFetcher("lower", blocks, -gatherSlack) 717 bodyFetcher := tester.makeBodyFetcher("lower", blocks, 0) 718 719 fetching := make(chan struct{}, 2) 720 tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} } 721 722 // Ensure that a block with a lower number than the threshold is discarded 723 tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 724 select { 725 case <-time.After(50 * time.Millisecond): 726 case <-fetching: 727 t.Fatalf("fetcher requested stale header") 728 } 729 // Ensure that a block with a higher number than the threshold is discarded 730 tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 731 select { 732 case <-time.After(50 * time.Millisecond): 733 case <-fetching: 734 t.Fatalf("fetcher requested future header") 735 } 736 } 737 738 // Tests that peers announcing blocks with invalid numbers (i.e. not matching 739 // the headers provided afterwards) get dropped as malicious. 740 func TestFullInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, false) } 741 func TestLightInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, true) } 742 743 func testInvalidNumberAnnouncement(t *testing.T, light bool) { 744 // Create a single block to import and check numbers against 745 hashes, blocks := makeChain(1, 0, genesis) 746 747 tester := newTester(light) 748 badHeaderFetcher := tester.makeHeaderFetcher("bad", blocks, -gatherSlack) 749 badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0) 750 751 imported := make(chan interface{}) 752 announced := make(chan interface{}, 2) 753 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { 754 if light { 755 if header == nil { 756 t.Fatalf("Fetcher try to import empty header") 757 } 758 imported <- header 759 } else { 760 if block == nil { 761 t.Fatalf("Fetcher try to import empty block") 762 } 763 imported <- block 764 } 765 } 766 // Announce a block with a bad number, check for immediate drop 767 tester.fetcher.announceChangeHook = func(hash common.Hash, b bool) { 768 announced <- nil 769 } 770 tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher) 771 verifyAnnounce := func() { 772 for i := 0; i < 2; i++ { 773 select { 774 case <-announced: 775 continue 776 case <-time.After(1 * time.Second): 777 t.Fatal("announce timeout") 778 return 779 } 780 } 781 } 782 verifyAnnounce() 783 verifyImportEvent(t, imported, false) 784 tester.lock.RLock() 785 dropped := tester.drops["bad"] 786 tester.lock.RUnlock() 787 788 if !dropped { 789 t.Fatalf("peer with invalid numbered announcement not dropped") 790 } 791 goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack) 792 goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0) 793 // Make sure a good announcement passes without a drop 794 tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher) 795 verifyAnnounce() 796 verifyImportEvent(t, imported, true) 797 798 tester.lock.RLock() 799 dropped = tester.drops["good"] 800 tester.lock.RUnlock() 801 802 if dropped { 803 t.Fatalf("peer with valid numbered announcement dropped") 804 } 805 verifyImportDone(t, imported) 806 } 807 808 // Tests that if a block is empty (i.e. header only), no body request should be 809 // made, and instead the header should be assembled into a whole block in itself. 810 func TestEmptyBlockShortCircuit(t *testing.T) { 811 // Create a chain of blocks to import 812 hashes, blocks := makeChain(32, 0, genesis) 813 814 tester := newTester(false) 815 defer tester.fetcher.Stop() 816 headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 817 bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 818 819 // Add a monitoring hook for all internal events 820 fetching := make(chan []common.Hash) 821 tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes } 822 823 completing := make(chan []common.Hash) 824 tester.fetcher.completingHook = func(hashes []common.Hash) { completing <- hashes } 825 826 imported := make(chan interface{}) 827 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { 828 if block == nil { 829 t.Fatalf("Fetcher try to import empty block") 830 } 831 imported <- block 832 } 833 // Iteratively announce blocks until all are imported 834 for i := len(hashes) - 2; i >= 0; i-- { 835 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) 836 837 // All announces should fetch the header 838 verifyFetchingEvent(t, fetching, true) 839 840 // Only blocks with data contents should request bodies 841 verifyCompletingEvent(t, completing, len(blocks[hashes[i]].Transactions()) > 0 || len(blocks[hashes[i]].Uncles()) > 0) 842 843 // Irrelevant of the construct, import should succeed 844 verifyImportEvent(t, imported, true) 845 } 846 verifyImportDone(t, imported) 847 } 848 849 // Tests that a peer is unable to use unbounded memory with sending infinite 850 // block announcements to a node, but that even in the face of such an attack, 851 // the fetcher remains operational. 852 func TestHashMemoryExhaustionAttack(t *testing.T) { 853 // Create a tester with instrumented import hooks 854 tester := newTester(false) 855 856 imported, announces := make(chan interface{}), atomic.Int32{} 857 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } 858 tester.fetcher.announceChangeHook = func(hash common.Hash, added bool) { 859 if added { 860 announces.Add(1) 861 } else { 862 announces.Add(-1) 863 } 864 } 865 // Create a valid chain and an infinite junk chain 866 targetBlocks := hashLimit + 2*maxQueueDist 867 hashes, blocks := makeChain(targetBlocks, 0, genesis) 868 validHeaderFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) 869 validBodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) 870 871 attack, _ := makeChain(targetBlocks, 0, unknownBlock) 872 attackerHeaderFetcher := tester.makeHeaderFetcher("attacker", nil, -gatherSlack) 873 attackerBodyFetcher := tester.makeBodyFetcher("attacker", nil, 0) 874 875 // Feed the tester a huge hashset from the attacker, and a limited from the valid peer 876 for i := 0; i < len(attack); i++ { 877 if i < maxQueueDist { 878 tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher) 879 } 880 tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher) 881 } 882 if count := announces.Load(); count != hashLimit+maxQueueDist { 883 t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) 884 } 885 // Wait for fetches to complete 886 verifyImportCount(t, imported, maxQueueDist) 887 888 // Feed the remaining valid hashes to ensure DOS protection state remains clean 889 for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- { 890 tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher) 891 verifyImportEvent(t, imported, true) 892 } 893 verifyImportDone(t, imported) 894 } 895 896 // Tests that blocks sent to the fetcher (either through propagation or via hash 897 // announces and retrievals) don't pile up indefinitely, exhausting available 898 // system memory. 899 func TestBlockMemoryExhaustionAttack(t *testing.T) { 900 // Create a tester with instrumented import hooks 901 tester := newTester(false) 902 903 imported, enqueued := make(chan interface{}), atomic.Int32{} 904 tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } 905 tester.fetcher.queueChangeHook = func(hash common.Hash, added bool) { 906 if added { 907 enqueued.Add(1) 908 } else { 909 enqueued.Add(-1) 910 } 911 } 912 // Create a valid chain and a batch of dangling (but in range) blocks 913 targetBlocks := hashLimit + 2*maxQueueDist 914 hashes, blocks := makeChain(targetBlocks, 0, genesis) 915 attack := make(map[common.Hash]*types.Block) 916 for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ { 917 hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock) 918 for _, hash := range hashes[:maxQueueDist-2] { 919 attack[hash] = blocks[hash] 920 } 921 } 922 // Try to feed all the attacker blocks make sure only a limited batch is accepted 923 for _, block := range attack { 924 tester.fetcher.Enqueue("attacker", block) 925 } 926 time.Sleep(200 * time.Millisecond) 927 if queued := enqueued.Load(); queued != blockLimit { 928 t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit) 929 } 930 // Queue up a batch of valid blocks, and check that a new peer is allowed to do so 931 for i := 0; i < maxQueueDist-1; i++ { 932 tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]]) 933 } 934 time.Sleep(100 * time.Millisecond) 935 if queued := enqueued.Load(); queued != blockLimit+maxQueueDist-1 { 936 t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1) 937 } 938 // Insert the missing piece (and sanity check the import) 939 tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2]]) 940 verifyImportCount(t, imported, maxQueueDist) 941 942 // Insert the remaining blocks in chunks to ensure clean DOS protection 943 for i := maxQueueDist; i < len(hashes)-1; i++ { 944 tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2-i]]) 945 verifyImportEvent(t, imported, true) 946 } 947 verifyImportDone(t, imported) 948 }