Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/testing/src/consensus_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
SlotClockTest,
SSZTest,
StateTransitionTest,
SyncTest,
VerifySignaturesTest,
)
from .test_types import (
Expand Down Expand Up @@ -46,6 +47,7 @@
DiscoveryCryptoTestFiller = Type[DiscoveryCryptoTest]
JustifiabilityTestFiller = Type[JustifiabilityTest]
PoseidonPermutationTestFiller = Type[PoseidonPermutationTest]
SyncTestFiller = Type[SyncTest]

__all__ = [
# Public API
Expand All @@ -70,6 +72,7 @@
"DiscoveryCryptoTest",
"JustifiabilityTest",
"PoseidonPermutationTest",
"SyncTest",
# Test types
"BaseForkChoiceStep",
"TickStep",
Expand All @@ -93,4 +96,5 @@
"DiscoveryCryptoTestFiller",
"JustifiabilityTestFiller",
"PoseidonPermutationTestFiller",
"SyncTestFiller",
]
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .slot_clock import SlotClockTest
from .ssz import SSZTest
from .state_transition import StateTransitionTest
from .sync import SyncTest
from .verify_signatures import VerifySignaturesTest

__all__ = [
Expand All @@ -26,4 +27,5 @@
"DiscoveryCryptoTest",
"JustifiabilityTest",
"PoseidonPermutationTest",
"SyncTest",
]
76 changes: 76 additions & 0 deletions packages/testing/src/consensus_testing/test_fixtures/sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Sync layer test fixture format.

Emits JSON vectors for the client-facing sync helpers. Each vector
pins the expected verdict on a given input so clients can align their
sync-layer decisions bit-for-bit.
"""

from typing import Any, ClassVar

from lean_spec.subspecs.sync.checkpoint_sync import verify_checkpoint_state
from lean_spec.types import Uint64

from ..genesis import generate_pre_state
from .base import BaseConsensusFixture


class SyncTest(BaseConsensusFixture):
"""Fixture for sync-layer conformance.

Currently supports one operation:

- ``verify_checkpoint``: emits the SSZ-encoded anchor state plus the
verification verdict a client must produce.

JSON output: operation, input, output.
"""

format_name: ClassVar[str] = "sync"
description: ClassVar[str] = "Tests sync-layer helpers clients must reproduce"

operation: str
"""Sync operation: currently only verify_checkpoint."""

input: dict[str, Any]
"""Operation-specific input. See per-handler docstrings."""

output: dict[str, Any] = {}
"""Computed output. Filled by make_fixture."""

def make_fixture(self) -> "SyncTest":
"""Dispatch to the operation handler.

Returns:
A copy of this fixture with output populated.

Raises:
ValueError: If the operation name is unknown.
"""
if self.operation == "verify_checkpoint":
output = self._make_verify_checkpoint()
else:
raise ValueError(f"Unknown sync operation: {self.operation!r}")
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.

Input keys:

- ``numValidators``: number of validators in the genesis state.

Output:

- ``valid``: result of verify_checkpoint_state on the built state.
- ``stateBytes``: SSZ-encoded state hex, so clients can deserialize
and run their own verify_checkpoint_state.
- ``validatorCount``: echoed for diagnostic clarity.
"""
num_validators = int(self.input["numValidators"])
state = generate_pre_state(genesis_time=Uint64(0), num_validators=num_validators)
valid = verify_checkpoint_state(state)
return {
"valid": valid,
"stateBytes": "0x" + state.encode_bytes().hex(),
"validatorCount": num_validators,
}
Empty file.
57 changes: 57 additions & 0 deletions tests/consensus/devnet/sync/test_checkpoint_verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Checkpoint-sync state verification: known-answer vectors.

Pins the structural-validity verdict each client must produce when
fetching an anchor state from a checkpoint provider. The verdict is a
defence-in-depth check applied before the state seeds a fork-choice
store.
"""

import pytest
from consensus_testing import SyncTestFiller

pytestmark = pytest.mark.valid_until("Devnet")


def test_checkpoint_verify_rejects_empty_validator_set(
sync: SyncTestFiller,
) -> None:
"""A checkpoint state with zero validators is rejected.

A state without validators cannot produce blocks, so seeding a
fork-choice store with it would be useless and mask configuration
errors. Clients must refuse the anchor before any store setup.
"""
sync(
operation="verify_checkpoint",
input={"numValidators": 0},
)


def test_checkpoint_verify_accepts_small_validator_set(
sync: SyncTestFiller,
) -> None:
"""A checkpoint state with a small in-range validator set is accepted.

Four validators is the baseline size used throughout the consensus
test suite. Pins the happy path of the verifier so clients observe
the accepted branch in addition to the rejection branch above.
"""
sync(
operation="verify_checkpoint",
input={"numValidators": 4},
)


def test_checkpoint_verify_accepts_eight_validator_set(
sync: SyncTestFiller,
) -> None:
"""Eight-validator anchor state is accepted at the key-manager limit.

Matches the maximum-validator setup used by the existing fork-choice
and signature-verification suites. Pins the verdict at the upper
end of the practical test envelope.
"""
sync(
operation="verify_checkpoint",
input={"numValidators": 8},
)
Loading