github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/initial-sync/round_robin_test.go (about) 1 package initialsync 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/paulbellamy/ratecounter" 9 types "github.com/prysmaticlabs/eth2-types" 10 mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" 11 dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" 12 p2pt "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing" 13 eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 14 "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1/wrapper" 15 "github.com/prysmaticlabs/prysm/proto/interfaces" 16 "github.com/prysmaticlabs/prysm/shared/abool" 17 "github.com/prysmaticlabs/prysm/shared/sliceutil" 18 "github.com/prysmaticlabs/prysm/shared/testutil" 19 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 20 "github.com/prysmaticlabs/prysm/shared/testutil/require" 21 logTest "github.com/sirupsen/logrus/hooks/test" 22 ) 23 24 func TestService_roundRobinSync(t *testing.T) { 25 tests := []struct { 26 name string 27 currentSlot types.Slot 28 availableBlockSlots []types.Slot 29 expectedBlockSlots []types.Slot 30 peers []*peerData 31 }{ 32 { 33 name: "Single peer with no finalized blocks", 34 currentSlot: 2, 35 availableBlockSlots: makeSequence(1, 32), 36 expectedBlockSlots: makeSequence(1, 2), 37 peers: []*peerData{ 38 { 39 blocks: makeSequence(1, 2), 40 finalizedEpoch: 0, 41 headSlot: 2, 42 }, 43 }, 44 }, 45 { 46 name: "Multiple peers with no finalized blocks", 47 currentSlot: 2, 48 availableBlockSlots: makeSequence(1, 32), 49 expectedBlockSlots: makeSequence(1, 2), 50 peers: []*peerData{ 51 { 52 blocks: makeSequence(1, 2), 53 finalizedEpoch: 0, 54 headSlot: 2, 55 }, 56 { 57 blocks: makeSequence(1, 2), 58 finalizedEpoch: 0, 59 headSlot: 2, 60 }, 61 { 62 blocks: makeSequence(1, 2), 63 finalizedEpoch: 0, 64 headSlot: 2, 65 }, 66 }, 67 }, 68 { 69 name: "Single peer with all blocks", 70 currentSlot: 131, 71 availableBlockSlots: makeSequence(1, 192), 72 expectedBlockSlots: makeSequence(1, 131), 73 peers: []*peerData{ 74 { 75 blocks: makeSequence(1, 192), 76 finalizedEpoch: 1, 77 headSlot: 131, 78 }, 79 }, 80 }, 81 { 82 name: "Multiple peers with all blocks", 83 currentSlot: 131, 84 availableBlockSlots: makeSequence(1, 192), 85 expectedBlockSlots: makeSequence(1, 131), 86 peers: []*peerData{ 87 { 88 blocks: makeSequence(1, 192), 89 finalizedEpoch: 1, 90 headSlot: 131, 91 }, 92 { 93 blocks: makeSequence(1, 192), 94 finalizedEpoch: 1, 95 headSlot: 131, 96 }, 97 { 98 blocks: makeSequence(1, 192), 99 finalizedEpoch: 1, 100 headSlot: 131, 101 }, 102 { 103 blocks: makeSequence(1, 192), 104 finalizedEpoch: 1, 105 headSlot: 131, 106 }, 107 }, 108 }, 109 { 110 name: "Multiple peers with failures", 111 currentSlot: 320, // 10 epochs 112 availableBlockSlots: makeSequence(1, 384), 113 expectedBlockSlots: makeSequence(1, 320), 114 peers: []*peerData{ 115 { 116 blocks: makeSequence(1, 384), 117 finalizedEpoch: 8, 118 headSlot: 320, 119 }, 120 { 121 blocks: makeSequence(1, 384), 122 finalizedEpoch: 8, 123 headSlot: 320, 124 failureSlots: makeSequence(1, 32), // first epoch 125 }, 126 { 127 blocks: makeSequence(1, 384), 128 finalizedEpoch: 8, 129 headSlot: 320, 130 }, 131 { 132 blocks: makeSequence(1, 384), 133 finalizedEpoch: 8, 134 headSlot: 320, 135 }, 136 }, 137 }, 138 { 139 name: "Multiple peers with many skipped slots", 140 currentSlot: 1280, 141 availableBlockSlots: append(makeSequence(1, 64), makeSequence(1000, 1300)...), 142 expectedBlockSlots: append(makeSequence(1, 64), makeSequence(1000, 1280)...), 143 peers: []*peerData{ 144 { 145 blocks: append(makeSequence(1, 64), makeSequence(1000, 1300)...), 146 finalizedEpoch: 36, 147 headSlot: 1280, 148 }, 149 { 150 blocks: append(makeSequence(1, 64), makeSequence(1000, 1300)...), 151 finalizedEpoch: 36, 152 headSlot: 1280, 153 }, 154 { 155 blocks: append(makeSequence(1, 64), makeSequence(1000, 1300)...), 156 finalizedEpoch: 36, 157 headSlot: 1280, 158 }, 159 }, 160 }, 161 { 162 name: "Multiple peers with multiple failures", 163 currentSlot: 320, // 10 epochs 164 availableBlockSlots: makeSequence(1, 384), 165 expectedBlockSlots: makeSequence(1, 320), 166 peers: []*peerData{ 167 { 168 blocks: makeSequence(1, 384), 169 finalizedEpoch: 9, 170 headSlot: 384, 171 }, 172 { 173 blocks: makeSequence(1, 320), 174 finalizedEpoch: 9, 175 headSlot: 384, 176 failureSlots: makeSequence(1, 320), 177 }, 178 { 179 blocks: makeSequence(1, 320), 180 finalizedEpoch: 9, 181 headSlot: 384, 182 failureSlots: makeSequence(1, 320), 183 }, 184 { 185 blocks: makeSequence(1, 320), 186 finalizedEpoch: 9, 187 headSlot: 384, 188 failureSlots: makeSequence(1, 320), 189 }, 190 }, 191 }, 192 { 193 name: "Multiple peers with different finalized epoch", 194 currentSlot: 320, // 10 epochs 195 availableBlockSlots: makeSequence(1, 384), 196 expectedBlockSlots: makeSequence(1, 320), 197 peers: []*peerData{ 198 { 199 blocks: makeSequence(1, 384), 200 finalizedEpoch: 10, 201 headSlot: 384, 202 }, 203 { 204 blocks: makeSequence(1, 384), 205 finalizedEpoch: 10, 206 headSlot: 384, 207 }, 208 { 209 blocks: makeSequence(1, 256), 210 finalizedEpoch: 5, 211 headSlot: 256, 212 }, 213 { 214 blocks: makeSequence(1, 192), 215 finalizedEpoch: 2, 216 headSlot: 192, 217 }, 218 }, 219 }, 220 { 221 name: "Multiple peers with missing parent blocks", 222 currentSlot: 160, // 5 epochs 223 availableBlockSlots: makeSequence(1, 192), 224 expectedBlockSlots: makeSequence(1, 160), 225 peers: []*peerData{ 226 { 227 blocks: makeSequence(1, 192), 228 finalizedEpoch: 4, 229 headSlot: 160, 230 }, 231 { 232 blocks: append(makeSequence(1, 6), makeSequence(161, 165)...), 233 finalizedEpoch: 4, 234 headSlot: 160, 235 forkedPeer: true, 236 }, 237 { 238 blocks: makeSequence(1, 192), 239 finalizedEpoch: 4, 240 headSlot: 160, 241 }, 242 { 243 blocks: makeSequence(1, 192), 244 finalizedEpoch: 4, 245 headSlot: 160, 246 }, 247 { 248 blocks: makeSequence(1, 192), 249 finalizedEpoch: 4, 250 headSlot: 160, 251 }, 252 { 253 blocks: makeSequence(1, 192), 254 finalizedEpoch: 4, 255 headSlot: 160, 256 }, 257 { 258 blocks: makeSequence(1, 192), 259 finalizedEpoch: 4, 260 headSlot: 160, 261 }, 262 { 263 blocks: makeSequence(1, 192), 264 finalizedEpoch: 4, 265 headSlot: 160, 266 }, 267 }, 268 }, 269 } 270 271 for _, tt := range tests { 272 t.Run(tt.name, func(t *testing.T) { 273 if tt.availableBlockSlots == nil { 274 tt.availableBlockSlots = tt.expectedBlockSlots 275 } 276 cache.initializeRootCache(tt.availableBlockSlots, t) 277 278 p := p2pt.NewTestP2P(t) 279 beaconDB := dbtest.SetupDB(t) 280 281 connectPeers(t, p, tt.peers, p.Peers()) 282 cache.RLock() 283 genesisRoot := cache.rootCache[0] 284 cache.RUnlock() 285 286 err := beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(testutil.NewBeaconBlock())) 287 require.NoError(t, err) 288 289 st, err := testutil.NewBeaconState() 290 require.NoError(t, err) 291 mc := &mock.ChainService{ 292 State: st, 293 Root: genesisRoot[:], 294 DB: beaconDB, 295 FinalizedCheckPoint: ð.Checkpoint{ 296 Epoch: 0, 297 }, 298 } // no-op mock 299 s := &Service{ 300 ctx: context.Background(), 301 cfg: &Config{Chain: mc, P2P: p, DB: beaconDB}, 302 synced: abool.New(), 303 chainStarted: abool.NewBool(true), 304 } 305 assert.NoError(t, s.roundRobinSync(makeGenesisTime(tt.currentSlot))) 306 if s.cfg.Chain.HeadSlot() < tt.currentSlot { 307 t.Errorf("Head slot (%d) is less than expected currentSlot (%d)", s.cfg.Chain.HeadSlot(), tt.currentSlot) 308 } 309 assert.Equal(t, true, len(tt.expectedBlockSlots) <= len(mc.BlocksReceived), "Processes wrong number of blocks") 310 var receivedBlockSlots []types.Slot 311 for _, blk := range mc.BlocksReceived { 312 receivedBlockSlots = append(receivedBlockSlots, blk.Block().Slot()) 313 } 314 missing := sliceutil.NotSlot(sliceutil.IntersectionSlot(tt.expectedBlockSlots, receivedBlockSlots), tt.expectedBlockSlots) 315 if len(missing) > 0 { 316 t.Errorf("Missing blocks at slots %v", missing) 317 } 318 }) 319 } 320 } 321 322 func TestService_processBlock(t *testing.T) { 323 beaconDB := dbtest.SetupDB(t) 324 genesisBlk := testutil.NewBeaconBlock() 325 genesisBlkRoot, err := genesisBlk.Block.HashTreeRoot() 326 require.NoError(t, err) 327 err = beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(genesisBlk)) 328 require.NoError(t, err) 329 st, err := testutil.NewBeaconState() 330 require.NoError(t, err) 331 s := NewService(context.Background(), &Config{ 332 P2P: p2pt.NewTestP2P(t), 333 DB: beaconDB, 334 Chain: &mock.ChainService{ 335 State: st, 336 Root: genesisBlkRoot[:], 337 DB: beaconDB, 338 FinalizedCheckPoint: ð.Checkpoint{ 339 Epoch: 0, 340 }, 341 }, 342 StateNotifier: &mock.MockStateNotifier{}, 343 }) 344 ctx := context.Background() 345 genesis := makeGenesisTime(32) 346 347 t.Run("process duplicate block", func(t *testing.T) { 348 blk1 := testutil.NewBeaconBlock() 349 blk1.Block.Slot = 1 350 blk1.Block.ParentRoot = genesisBlkRoot[:] 351 blk1Root, err := blk1.Block.HashTreeRoot() 352 require.NoError(t, err) 353 blk2 := testutil.NewBeaconBlock() 354 blk2.Block.Slot = 2 355 blk2.Block.ParentRoot = blk1Root[:] 356 357 // Process block normally. 358 err = s.processBlock(ctx, genesis, wrapper.WrappedPhase0SignedBeaconBlock(blk1), func( 359 ctx context.Context, block interfaces.SignedBeaconBlock, blockRoot [32]byte) error { 360 assert.NoError(t, s.cfg.Chain.ReceiveBlock(ctx, block, blockRoot)) 361 return nil 362 }) 363 assert.NoError(t, err) 364 365 // Duplicate processing should trigger error. 366 err = s.processBlock(ctx, genesis, wrapper.WrappedPhase0SignedBeaconBlock(blk1), func( 367 ctx context.Context, block interfaces.SignedBeaconBlock, blockRoot [32]byte) error { 368 return nil 369 }) 370 assert.ErrorContains(t, errBlockAlreadyProcessed.Error(), err) 371 372 // Continue normal processing, should proceed w/o errors. 373 err = s.processBlock(ctx, genesis, wrapper.WrappedPhase0SignedBeaconBlock(blk2), func( 374 ctx context.Context, block interfaces.SignedBeaconBlock, blockRoot [32]byte) error { 375 assert.NoError(t, s.cfg.Chain.ReceiveBlock(ctx, block, blockRoot)) 376 return nil 377 }) 378 assert.NoError(t, err) 379 assert.Equal(t, types.Slot(2), s.cfg.Chain.HeadSlot(), "Unexpected head slot") 380 }) 381 } 382 383 func TestService_processBlockBatch(t *testing.T) { 384 beaconDB := dbtest.SetupDB(t) 385 genesisBlk := testutil.NewBeaconBlock() 386 genesisBlkRoot, err := genesisBlk.Block.HashTreeRoot() 387 require.NoError(t, err) 388 err = beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(genesisBlk)) 389 require.NoError(t, err) 390 st, err := testutil.NewBeaconState() 391 require.NoError(t, err) 392 s := NewService(context.Background(), &Config{ 393 P2P: p2pt.NewTestP2P(t), 394 DB: beaconDB, 395 Chain: &mock.ChainService{ 396 State: st, 397 Root: genesisBlkRoot[:], 398 DB: beaconDB, 399 FinalizedCheckPoint: ð.Checkpoint{ 400 Epoch: 0, 401 }, 402 }, 403 StateNotifier: &mock.MockStateNotifier{}, 404 }) 405 ctx := context.Background() 406 genesis := makeGenesisTime(32) 407 408 t.Run("process non-linear batch", func(t *testing.T) { 409 var batch []interfaces.SignedBeaconBlock 410 currBlockRoot := genesisBlkRoot 411 for i := types.Slot(1); i < 10; i++ { 412 parentRoot := currBlockRoot 413 blk1 := testutil.NewBeaconBlock() 414 blk1.Block.Slot = i 415 blk1.Block.ParentRoot = parentRoot[:] 416 blk1Root, err := blk1.Block.HashTreeRoot() 417 require.NoError(t, err) 418 err = beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk1)) 419 require.NoError(t, err) 420 batch = append(batch, wrapper.WrappedPhase0SignedBeaconBlock(blk1)) 421 currBlockRoot = blk1Root 422 } 423 424 var batch2 []interfaces.SignedBeaconBlock 425 for i := types.Slot(10); i < 20; i++ { 426 parentRoot := currBlockRoot 427 blk1 := testutil.NewBeaconBlock() 428 blk1.Block.Slot = i 429 blk1.Block.ParentRoot = parentRoot[:] 430 blk1Root, err := blk1.Block.HashTreeRoot() 431 require.NoError(t, err) 432 err = beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk1)) 433 require.NoError(t, err) 434 batch2 = append(batch2, wrapper.WrappedPhase0SignedBeaconBlock(blk1)) 435 currBlockRoot = blk1Root 436 } 437 438 // Process block normally. 439 err = s.processBatchedBlocks(ctx, genesis, batch, func( 440 ctx context.Context, blks []interfaces.SignedBeaconBlock, blockRoots [][32]byte) error { 441 assert.NoError(t, s.cfg.Chain.ReceiveBlockBatch(ctx, blks, blockRoots)) 442 return nil 443 }) 444 assert.NoError(t, err) 445 446 // Duplicate processing should trigger error. 447 err = s.processBatchedBlocks(ctx, genesis, batch, func( 448 ctx context.Context, blocks []interfaces.SignedBeaconBlock, blockRoots [][32]byte) error { 449 return nil 450 }) 451 assert.ErrorContains(t, "no good blocks in batch", err) 452 453 var badBatch2 []interfaces.SignedBeaconBlock 454 for i, b := range batch2 { 455 // create a non-linear batch 456 if i%3 == 0 && i != 0 { 457 continue 458 } 459 badBatch2 = append(badBatch2, b) 460 } 461 462 // Bad batch should fail because it is non linear 463 err = s.processBatchedBlocks(ctx, genesis, badBatch2, func( 464 ctx context.Context, blks []interfaces.SignedBeaconBlock, blockRoots [][32]byte) error { 465 return nil 466 }) 467 expectedSubErr := "expected linear block list" 468 assert.ErrorContains(t, expectedSubErr, err) 469 470 // Continue normal processing, should proceed w/o errors. 471 err = s.processBatchedBlocks(ctx, genesis, batch2, func( 472 ctx context.Context, blks []interfaces.SignedBeaconBlock, blockRoots [][32]byte) error { 473 assert.NoError(t, s.cfg.Chain.ReceiveBlockBatch(ctx, blks, blockRoots)) 474 return nil 475 }) 476 assert.NoError(t, err) 477 assert.Equal(t, types.Slot(19), s.cfg.Chain.HeadSlot(), "Unexpected head slot") 478 }) 479 } 480 481 func TestService_blockProviderScoring(t *testing.T) { 482 cache.initializeRootCache(makeSequence(1, 640), t) 483 484 p := p2pt.NewTestP2P(t) 485 beaconDB := dbtest.SetupDB(t) 486 487 peerData := []*peerData{ 488 { 489 // The slowest peer, only a single block in couple of epochs. 490 blocks: []types.Slot{1, 65, 129}, 491 finalizedEpoch: 5, 492 headSlot: 160, 493 }, 494 { 495 // A relatively slow peer, still should perform better than the slowest peer. 496 blocks: append([]types.Slot{1, 2, 3, 4, 65, 66, 67, 68, 129, 130}, makeSequence(131, 160)...), 497 finalizedEpoch: 5, 498 headSlot: 160, 499 }, 500 { 501 // This peer has all blocks - should be a preferred one. 502 blocks: makeSequence(1, 320), 503 finalizedEpoch: 5, 504 headSlot: 160, 505 }, 506 } 507 508 peer1 := connectPeer(t, p, peerData[0], p.Peers()) 509 peer2 := connectPeer(t, p, peerData[1], p.Peers()) 510 peer3 := connectPeer(t, p, peerData[2], p.Peers()) 511 512 cache.RLock() 513 genesisRoot := cache.rootCache[0] 514 cache.RUnlock() 515 516 err := beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(testutil.NewBeaconBlock())) 517 require.NoError(t, err) 518 519 st, err := testutil.NewBeaconState() 520 require.NoError(t, err) 521 require.NoError(t, err) 522 mc := &mock.ChainService{ 523 State: st, 524 Root: genesisRoot[:], 525 DB: beaconDB, 526 FinalizedCheckPoint: ð.Checkpoint{ 527 Epoch: 0, 528 Root: make([]byte, 32), 529 }, 530 } // no-op mock 531 s := &Service{ 532 ctx: context.Background(), 533 cfg: &Config{Chain: mc, P2P: p, DB: beaconDB}, 534 synced: abool.New(), 535 chainStarted: abool.NewBool(true), 536 } 537 scorer := s.cfg.P2P.Peers().Scorers().BlockProviderScorer() 538 expectedBlockSlots := makeSequence(1, 160) 539 currentSlot := types.Slot(160) 540 541 assert.Equal(t, scorer.MaxScore(), scorer.Score(peer1)) 542 assert.Equal(t, scorer.MaxScore(), scorer.Score(peer2)) 543 assert.Equal(t, scorer.MaxScore(), scorer.Score(peer3)) 544 545 assert.NoError(t, s.roundRobinSync(makeGenesisTime(currentSlot))) 546 if s.cfg.Chain.HeadSlot() < currentSlot { 547 t.Errorf("Head slot (%d) is less than expected currentSlot (%d)", s.cfg.Chain.HeadSlot(), currentSlot) 548 } 549 assert.Equal(t, true, len(expectedBlockSlots) <= len(mc.BlocksReceived), "Processes wrong number of blocks") 550 var receivedBlockSlots []types.Slot 551 for _, blk := range mc.BlocksReceived { 552 receivedBlockSlots = append(receivedBlockSlots, blk.Block().Slot()) 553 } 554 missing := sliceutil.NotSlot(sliceutil.IntersectionSlot(expectedBlockSlots, receivedBlockSlots), expectedBlockSlots) 555 if len(missing) > 0 { 556 t.Errorf("Missing blocks at slots %v", missing) 557 } 558 559 // Increment all peers' stats, so that nobody is boosted (as new, not yet used peer). 560 scorer.IncrementProcessedBlocks(peer1, 1) 561 scorer.IncrementProcessedBlocks(peer2, 1) 562 scorer.IncrementProcessedBlocks(peer3, 1) 563 score1 := scorer.Score(peer1) 564 score2 := scorer.Score(peer2) 565 score3 := scorer.Score(peer3) 566 assert.Equal(t, true, score1 < score3, "Incorrect score (%v) for peer: %v (must be lower than %v)", score1, peer1, score3) 567 assert.Equal(t, true, score2 < score3, "Incorrect score (%v) for peer: %v (must be lower than %v)", score2, peer2, score3) 568 assert.Equal(t, true, scorer.ProcessedBlocks(peer3) > 100, "Not enough blocks returned by healthy peer: %d", scorer.ProcessedBlocks(peer3)) 569 } 570 571 func TestService_syncToFinalizedEpoch(t *testing.T) { 572 cache.initializeRootCache(makeSequence(1, 640), t) 573 574 p := p2pt.NewTestP2P(t) 575 beaconDB := dbtest.SetupDB(t) 576 cache.RLock() 577 genesisRoot := cache.rootCache[0] 578 cache.RUnlock() 579 580 err := beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(testutil.NewBeaconBlock())) 581 require.NoError(t, err) 582 583 st, err := testutil.NewBeaconState() 584 require.NoError(t, err) 585 mc := &mock.ChainService{ 586 State: st, 587 Root: genesisRoot[:], 588 DB: beaconDB, 589 FinalizedCheckPoint: ð.Checkpoint{ 590 Epoch: 0, 591 Root: make([]byte, 32), 592 }, 593 } 594 s := &Service{ 595 ctx: context.Background(), 596 cfg: &Config{Chain: mc, P2P: p, DB: beaconDB}, 597 synced: abool.New(), 598 chainStarted: abool.NewBool(true), 599 counter: ratecounter.NewRateCounter(counterSeconds * time.Second), 600 } 601 expectedBlockSlots := makeSequence(1, 191) 602 currentSlot := types.Slot(191) 603 604 // Sync to finalized epoch. 605 hook := logTest.NewGlobal() 606 connectPeer(t, p, &peerData{ 607 blocks: makeSequence(1, 240), 608 finalizedEpoch: 5, 609 headSlot: 195, 610 }, p.Peers()) 611 genesis := makeGenesisTime(currentSlot) 612 assert.NoError(t, s.syncToFinalizedEpoch(context.Background(), genesis)) 613 if s.cfg.Chain.HeadSlot() < currentSlot { 614 t.Errorf("Head slot (%d) is less than expected currentSlot (%d)", s.cfg.Chain.HeadSlot(), currentSlot) 615 } 616 assert.Equal(t, true, len(expectedBlockSlots) <= len(mc.BlocksReceived), "Processes wrong number of blocks") 617 var receivedBlockSlots []types.Slot 618 for _, blk := range mc.BlocksReceived { 619 receivedBlockSlots = append(receivedBlockSlots, blk.Block().Slot()) 620 } 621 missing := sliceutil.NotSlot(sliceutil.IntersectionSlot(expectedBlockSlots, receivedBlockSlots), expectedBlockSlots) 622 if len(missing) > 0 { 623 t.Errorf("Missing blocks at slots %v", missing) 624 } 625 assert.LogsDoNotContain(t, hook, "Already synced to finalized epoch") 626 627 // Try to re-sync, should be exited immediately (node is already synced to finalized epoch). 628 hook.Reset() 629 assert.NoError(t, s.syncToFinalizedEpoch(context.Background(), genesis)) 630 assert.LogsContain(t, hook, "Already synced to finalized epoch") 631 }