diff --git a/packages/testing/src/consensus_testing/test_fixtures/sync.py b/packages/testing/src/consensus_testing/test_fixtures/sync.py index 87493322d..10e08a680 100644 --- a/packages/testing/src/consensus_testing/test_fixtures/sync.py +++ b/packages/testing/src/consensus_testing/test_fixtures/sync.py @@ -7,10 +7,11 @@ from typing import Any, ClassVar +from lean_spec.subspecs.containers.slot import Slot from lean_spec.subspecs.sync.checkpoint_sync import verify_checkpoint_state from lean_spec.types import Uint64 -from ..genesis import generate_pre_state +from ..genesis import build_anchor, generate_pre_state from .base import BaseConsensusFixture @@ -53,11 +54,15 @@ def make_fixture(self) -> "SyncTest": return self.model_copy(update={"output": output}) def _make_verify_checkpoint(self) -> dict[str, Any]: - """Build a genesis state for the given validator count and report the verdict. + """Build a state for the given validator count and anchor slot and report the verdict. Input keys: - - ``numValidators``: number of validators in the genesis state. + - ``numValidators``: number of validators in the state. + - ``anchorSlot``: optional slot to advance the chain through before + verifying. Zero (default) yields a genesis state; positive values + walk an empty-block chain through the slot so historical_block_hashes + reflects a real advanced anchor. Output: @@ -65,12 +70,22 @@ def _make_verify_checkpoint(self) -> dict[str, Any]: - ``stateBytes``: SSZ-encoded state hex, so clients can deserialize and run their own verify_checkpoint_state. - ``validatorCount``: echoed for diagnostic clarity. + - ``anchorSlot``: echoed so consumers see exactly which state was verified. """ num_validators = int(self.input["numValidators"]) - state = generate_pre_state(genesis_time=Uint64(0), num_validators=num_validators) + anchor_slot = int(self.input.get("anchorSlot", 0)) + if anchor_slot == 0: + state = generate_pre_state(genesis_time=Uint64(0), num_validators=num_validators) + else: + state, _ = build_anchor( + num_validators=num_validators, + anchor_slot=Slot(anchor_slot), + genesis_time=Uint64(0), + ) valid = verify_checkpoint_state(state) return { "valid": valid, "stateBytes": "0x" + state.encode_bytes().hex(), "validatorCount": num_validators, + "anchorSlot": anchor_slot, } diff --git a/tests/consensus/devnet/sync/test_checkpoint_verify_advanced.py b/tests/consensus/devnet/sync/test_checkpoint_verify_advanced.py new file mode 100644 index 000000000..41065d7b7 --- /dev/null +++ b/tests/consensus/devnet/sync/test_checkpoint_verify_advanced.py @@ -0,0 +1,50 @@ +"""Checkpoint-sync verification vectors at post-genesis anchor slots. + +Existing verify_checkpoint vectors run against fresh genesis states. +These pin the verdict after the chain has advanced through empty +blocks, so clients' state deserialisation is exercised on the non-zero +historical_block_hashes path that real checkpoint-sync downloads hit. +""" + +import pytest +from consensus_testing import SyncTestFiller + +pytestmark = pytest.mark.valid_until("Devnet") + + +def test_checkpoint_verify_advanced_slot_three(sync: SyncTestFiller) -> None: + """Advanced anchor state at slot 3 with four validators is accepted. + + The chain walks through three empty blocks. The resulting state + carries non-empty historical_block_hashes and a non-zero latest + block header. Pins the accepted verdict plus the exact SSZ bytes. + """ + sync( + operation="verify_checkpoint", + input={"numValidators": 4, "anchorSlot": 3}, + ) + + +def test_checkpoint_verify_advanced_slot_ten(sync: SyncTestFiller) -> None: + """Advanced anchor state at slot 10 with four validators is accepted. + + Ten empty blocks populate historical_block_hashes and justified_slots + with longer lists. Pins the verdict and state bytes at a larger + history than the slot-three case. + """ + sync( + operation="verify_checkpoint", + input={"numValidators": 4, "anchorSlot": 10}, + ) + + +def test_checkpoint_verify_advanced_eight_validators(sync: SyncTestFiller) -> None: + """Advanced anchor state at slot 5 with eight validators is accepted. + + Exercises the combination of larger validator set with a non-zero + anchor slot so clients diff both axes in a single vector. + """ + sync( + operation="verify_checkpoint", + input={"numValidators": 8, "anchorSlot": 5}, + )