diff --git a/beacon/blsync/block_sync_test.go b/beacon/blsync/block_sync_test.go index e7c2c4d163..d9f24971d7 100644 --- a/beacon/blsync/block_sync_test.go +++ b/beacon/blsync/block_sync_test.go @@ -137,6 +137,7 @@ func TestBlockSync(t *testing.T) { type testHeadTracker struct { prefetch types.HeadInfo validated types.SignedHeader + finalityUpdate types.FinalityUpdate } func (h *testHeadTracker) PrefetchHead() types.HeadInfo { @@ -161,3 +162,152 @@ func (h *testHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) { SignatureSlot: h.validated.SignatureSlot, }, h.validated.Header != (types.Header{}) } + +func TestBlockSyncFinality(t *testing.T) { + ht := &testHeadTracker{} + blockSync := newBeaconBlockSync(ht) + headCh := make(chan types.ChainHeadEvent, 16) + blockSync.SubscribeChainHead(headCh) + ts := sync.NewTestScheduler(t, blockSync) + ts.AddServer(testServer1, 1) + ts.AddServer(testServer2, 1) + + // Helper function to check finality hash in chain head events + expFinalizedHash := func(expFinalizedHash common.Hash) { + t.Helper() + select { + case event := <-headCh: + if event.Finalized != expFinalizedHash { + t.Errorf("Wrong finalized hash, expected %x, got %x", expFinalizedHash, event.Finalized) + } + default: + t.Error("Expected chain head event with finalized hash, but none received") + } + } + + // Helper function to check no chain head event + expNoChainHeadEvent := func() { + t.Helper() + select { + case event := <-headCh: + t.Errorf("Unexpected chain head event received: finalized hash %x", event.Finalized) + default: + // Expected: no event + } + } + + // Create test blocks with different slot configurations + testBlock3 := types.NewBeaconBlock(&deneb.BeaconBlock{ + Slot: 127, // Slot 127 (epoch 3, slot 63) + Body: deneb.BeaconBlockBody{ + ExecutionPayload: deneb.ExecutionPayload{ + BlockNumber: 460, + BlockHash: zrntcommon.Hash32(common.HexToHash("a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1")), + }, + }, + }) + + testBlock4 := types.NewBeaconBlock(&deneb.BeaconBlock{ + Slot: 128, // Slot 128 (epoch 4, slot 0) + Body: deneb.BeaconBlockBody{ + ExecutionPayload: deneb.ExecutionPayload{ + BlockNumber: 461, + BlockHash: zrntcommon.Hash32(common.HexToHash("b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2")), + }, + }, + }) + + // Test case 1: No finality update available + // Set block 3 as validated head, no finality update + ht.validated.Header = testBlock3.Header() + ts.Run(1) + // Should not emit chain head event because no finality update is available + expNoChainHeadEvent() + + // Test case 2: Finality update available, same epoch + // Set finality update for the same epoch as attested header + ht.finalityUpdate = types.FinalityUpdate{ + Attested: types.HeaderWithExecProof{Header: testBlock3.Header()}, + Finalized: types.HeaderWithExecProof{ + Header: types.Header{ + Slot: 127, + StateRoot: common.HexToHash("f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2"), + }, + PayloadHeader: types.NewExecutionHeader(&deneb.ExecutionPayloadHeader{ + BlockHash: zrntcommon.Hash32(common.HexToHash("f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2")), + }), + }, + Signature: ht.validated.Signature, + SignatureSlot: ht.validated.SignatureSlot, + } + ts.Run(2) + // Should emit chain head event with finalized hash + expFinalizedHash(common.HexToHash("f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2")) + + // Test case 3: Finality update available, attested epoch < finalized epoch + // This should not emit chain head event + ht.validated.Header = testBlock3.Header() // Slot 127 (epoch 3) + ht.finalityUpdate = types.FinalityUpdate{ + Attested: types.HeaderWithExecProof{Header: testBlock3.Header()}, + Finalized: types.HeaderWithExecProof{ + Header: types.Header{ + Slot: 128, + StateRoot: common.HexToHash("e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2d3"), + }, + PayloadHeader: types.NewExecutionHeader(&deneb.ExecutionPayloadHeader{ + BlockHash: zrntcommon.Hash32(common.HexToHash("e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2d3")), + }), + }, + Signature: ht.validated.Signature, + SignatureSlot: ht.validated.SignatureSlot, + } + ts.Run(3) + // Should not emit chain head event because attested epoch < finalized epoch + expNoChainHeadEvent() + + // Test case 4: Finality update available, attested epoch == finalized epoch + 1 + // Set block 4 as validated head (epoch 4, slot 0) + ht.validated.Header = testBlock4.Header() + // Set finalized header to epoch 3 (slot 127) + ht.finalityUpdate.Finalized.Header.Slot = 127 + ts.Run(4) + // Should not emit chain head event because head is at first slot of next epoch + // and we need to wait for finality update + expNoChainHeadEvent() + + // Test case 5: Finality update available, attested epoch == finalized epoch + 1 + // but parent block is not in the same epoch as finalized + // Set block 4 as validated head (epoch 4, slot 0) + ht.validated.Header = testBlock4.Header() + // Set finalized header to epoch 3 (slot 127) + ht.finalityUpdate.Finalized.Header.Slot = 127 + // Set finalized payload header for test case 5 + ht.finalityUpdate.Finalized.PayloadHeader = types.NewExecutionHeader(&deneb.ExecutionPayloadHeader{ + BlockHash: zrntcommon.Hash32(common.HexToHash("e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2d3")), + }) + // Add parent block (block 3) to recent blocks + blockSync.recentBlocks.Add(testBlock3.Root(), testBlock3) + ts.Run(5) + // Should emit chain head event because parent block is available and not in finalized epoch + expFinalizedHash(common.HexToHash("e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2d3")) + + // Test case 6: Finality update available, attested epoch == finalized epoch + 1 + // but parent block is in the same epoch as finalized + // Create a block in epoch 2 (slot 95) + testBlock5 := types.NewBeaconBlock(&deneb.BeaconBlock{ + Slot: 95, // Slot 95 (epoch 2, slot 63) + Body: deneb.BeaconBlockBody{ + ExecutionPayload: deneb.ExecutionPayload{ + BlockNumber: 462, + BlockHash: zrntcommon.Hash32(common.HexToHash("c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3")), + }, + }, + }) + ht.validated.Header = testBlock4.Header() // Slot 128 (epoch 4, slot 0) + ht.finalityUpdate.Finalized.Header.Slot = 127 // Epoch 3, slot 63 + // Add parent block (block 5) to recent blocks - this is in epoch 2 + blockSync.recentBlocks.Add(testBlock5.Root(), testBlock5) + ts.Run(6) + // Should not emit chain head event because parent is in finalized epoch + expNoChainHeadEvent() +}