diff --git a/packages/testing/src/consensus_testing/test_types/block_spec.py b/packages/testing/src/consensus_testing/test_types/block_spec.py index ce857f7b..795a9031 100644 --- a/packages/testing/src/consensus_testing/test_types/block_spec.py +++ b/packages/testing/src/consensus_testing/test_types/block_spec.py @@ -22,9 +22,9 @@ SignedBlock, SingleMessageAggregate, State, + Store, ) from lean_spec.spec.forks.lstar.spec import LstarSpec -from lean_spec.spec.forks.lstar.store import Store from lean_spec.spec.ssz import ByteList512KiB, Bytes32 from ..keys import XmssKeyManager, create_dummy_signature diff --git a/packages/testing/src/consensus_testing/test_types/gossip_attestation_spec.py b/packages/testing/src/consensus_testing/test_types/gossip_attestation_spec.py index 2506e38e..a2b7f4e3 100644 --- a/packages/testing/src/consensus_testing/test_types/gossip_attestation_spec.py +++ b/packages/testing/src/consensus_testing/test_types/gossip_attestation_spec.py @@ -5,9 +5,8 @@ from lean_spec.base import CamelModel from lean_spec.spec.crypto.merkleization import hash_tree_root from lean_spec.spec.forks import Checkpoint, Slot, ValidatorIndex -from lean_spec.spec.forks.lstar.containers import AttestationData, Block, SignedAttestation +from lean_spec.spec.forks.lstar.containers import AttestationData, Block, SignedAttestation, Store from lean_spec.spec.forks.lstar.spec import LstarSpec -from lean_spec.spec.forks.lstar.store import Store from lean_spec.spec.ssz import Bytes32 from ..keys import XmssKeyManager, create_dummy_signature diff --git a/packages/testing/src/consensus_testing/test_types/store_checks.py b/packages/testing/src/consensus_testing/test_types/store_checks.py index c222da31..36e994a9 100644 --- a/packages/testing/src/consensus_testing/test_types/store_checks.py +++ b/packages/testing/src/consensus_testing/test_types/store_checks.py @@ -6,9 +6,8 @@ from lean_spec.node.chain.clock import Interval from lean_spec.spec.crypto.merkleization import hash_tree_root from lean_spec.spec.forks import Slot, ValidatorIndex -from lean_spec.spec.forks.lstar.containers import AttestationData, Block +from lean_spec.spec.forks.lstar.containers import AttestationData, Block, Store from lean_spec.spec.forks.lstar.spec import LstarSpec -from lean_spec.spec.forks.lstar.store import Store from lean_spec.spec.ssz import ZERO_HASH, Bytes32 from .utils import resolve_block_root diff --git a/src/lean_spec/spec/forks/__init__.py b/src/lean_spec/spec/forks/__init__.py index 29b5fe1c..acf13c50 100644 --- a/src/lean_spec/spec/forks/__init__.py +++ b/src/lean_spec/spec/forks/__init__.py @@ -8,6 +8,7 @@ AggregationBits, Attestation, AttestationData, + AttestationSignatureEntry, Block, BlockBody, BlockHeader, @@ -25,7 +26,6 @@ Validators, ) from .lstar.spec import LstarSpec, LstarStore -from .lstar.store import AttestationSignatureEntry from .protocol import ForkProtocol, SpecStateType, SpecStoreType from .registry import ForkRegistry diff --git a/src/lean_spec/spec/forks/lstar/__init__.py b/src/lean_spec/spec/forks/lstar/__init__.py index c860498e..f1378e07 100644 --- a/src/lean_spec/spec/forks/lstar/__init__.py +++ b/src/lean_spec/spec/forks/lstar/__init__.py @@ -1,7 +1,6 @@ """Lstar fork""" -from .containers import State +from .containers import AttestationSignatureEntry, State from .spec import LstarStore as Store -from .store import AttestationSignatureEntry __all__ = ["AttestationSignatureEntry", "State", "Store"] diff --git a/src/lean_spec/spec/forks/lstar/containers.py b/src/lean_spec/spec/forks/lstar/containers.py index 1dc2e80b..6240ef6d 100644 --- a/src/lean_spec/spec/forks/lstar/containers.py +++ b/src/lean_spec/spec/forks/lstar/containers.py @@ -1,6 +1,6 @@ """The container types for the Lean consensus specification.""" -from typing import Final, Self +from typing import Final, NamedTuple, Self from lean_multisig_py import ( aggregate_type_1, @@ -9,8 +9,11 @@ verify_type_1, verify_type_2_with_messages, ) +from pydantic import Field +from lean_spec.base import StrictBaseModel from lean_spec.config import LEAN_ENV +from lean_spec.node.chain.clock import Interval from lean_spec.node.chain.config import HISTORICAL_ROOTS_LIMIT from lean_spec.spec.crypto.xmss.containers import PublicKey, Signature from lean_spec.spec.ssz import Boolean, ByteList512KiB, Bytes32, Bytes52, Container, SSZList, Uint64 @@ -860,3 +863,129 @@ class State(Container): justifications_validators: JustificationValidators """A bitlist of validators who participated in justifications.""" + + +class AttestationSignatureEntry(NamedTuple): + """ + Single validator's XMSS signature for an attestation. + + Used as an element in the attestation_signatures map: one entry per validator + that attested to the same AttestationData. + """ + + validator_index: ValidatorIndex + signature: Signature + + +class Store[StateT: Container, BlockT: Container](StrictBaseModel): + """ + Forkchoice store tracking chain state and validator attestations. + + This is the "local view" that a node uses to run LMD GHOST. It contains: + + - which blocks and states are known, + - which checkpoints are justified and finalized, + - which block is currently considered the head, + - and, for each validator, their latest attestation that should influence fork choice. + + The `Store` is updated whenever: + - a new block is processed, + - an attestation is received (via a block or gossip), + - an interval tick occurs (activating new attestations), + - or when the head is recomputed. + """ + + time: Interval + """Current time in intervals since genesis.""" + + config: Config + """Chain configuration parameters.""" + + head: Bytes32 + """ + Root of the current canonical chain head block. + + This is the result of running the fork choice algorithm on the current contents of the `Store`. + """ + + safe_target: Bytes32 + """ + Root of the current safe target for attestation. + + This can be used by higher-level logic to restrict which blocks are + considered safe to attest to, based on additional safety conditions. + """ + + latest_justified: Checkpoint + """ + Highest slot justified checkpoint known to the store. + + LMD GHOST starts from this checkpoint when computing the head. + + Only descendants of this checkpoint are considered viable. + """ + + latest_finalized: Checkpoint + """ + Highest slot finalized checkpoint known to the store. + + Everything strictly before this checkpoint can be considered immutable. + + Fork choice will never revert finalized history. + """ + + blocks: dict[Bytes32, BlockT] = Field(default_factory=dict) + """ + Mapping from block root to Block objects. + + This is the set of blocks that the node currently knows about. + + Every block that might participate in fork choice must appear here. + """ + + states: dict[Bytes32, StateT] = Field(default_factory=dict) + """ + Mapping from block root to State objects. + + For each known block, we keep its post-state. + + These states carry justified and finalized checkpoints that we use to update the + `Store`'s latest justified and latest finalized checkpoints. + """ + + validator_index: ValidatorIndex | None + """Index of the validator running this store instance.""" + + attestation_signatures: dict[AttestationData, set[AttestationSignatureEntry]] = Field( + default_factory=dict + ) + """ + Per-validator XMSS signatures learned from committee attesters. + + Keyed by AttestationData. + """ + + latest_new_aggregated_payloads: dict[AttestationData, set[SingleMessageAggregate]] = Field( + default_factory=dict + ) + """ + Aggregated signature proofs pending processing. + + These payloads are "new" and do not yet contribute to fork choice. + They migrate to known payloads via interval ticks. + Populated from gossip aggregated attestations. + Block import does not feed individual proofs into this map directly. + The block-level proof is a merged multi-message aggregate blob verified as a whole. + On gossip-block import, any validator deconstructs that multi-message aggregate into + per-message proofs, writes them back here, and gossips the aggregate. + """ + + latest_known_aggregated_payloads: dict[AttestationData, set[SingleMessageAggregate]] = Field( + default_factory=dict + ) + """ + Aggregated signature proofs that have been processed. + + These payloads are "known" and contribute to fork choice weights. + Used for recursive signature aggregation when building blocks. + """ diff --git a/src/lean_spec/spec/forks/lstar/spec.py b/src/lean_spec/spec/forks/lstar/spec.py index 3890e87e..3a39dead 100644 --- a/src/lean_spec/spec/forks/lstar/spec.py +++ b/src/lean_spec/spec/forks/lstar/spec.py @@ -27,6 +27,7 @@ AggregatedAttestations, AggregationError, AttestationData, + AttestationSignatureEntry, Block, BlockBody, BlockHeader, @@ -42,13 +43,13 @@ SingleMessageAggregate, Slot, State, + Store, ValidatorIndex, Validators, ) from lean_spec.spec.ssz import ZERO_HASH, Boolean, Bytes32, SSZList, Uint8, Uint64 from ..protocol import ForkProtocol, SpecBlockType, SpecStateType -from .store import AttestationSignatureEntry, Store LstarStore = Store[State, Block] """Concrete Store specialization owned by the lstar fork.""" diff --git a/src/lean_spec/spec/forks/lstar/store.py b/src/lean_spec/spec/forks/lstar/store.py deleted file mode 100644 index 812ed345..00000000 --- a/src/lean_spec/spec/forks/lstar/store.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Forkchoice store for tracking chain state and attestations. - -The Store tracks all information required for the LMD GHOST forkchoice algorithm. -""" - -__all__ = ["AttestationSignatureEntry", "Store"] - -from typing import NamedTuple - -from pydantic import Field - -from lean_spec.base import StrictBaseModel -from lean_spec.node.chain.clock import Interval -from lean_spec.spec.crypto.xmss.containers import Signature -from lean_spec.spec.forks.lstar.containers import ( - AttestationData, - Checkpoint, - Config, - SingleMessageAggregate, - ValidatorIndex, -) -from lean_spec.spec.ssz import Bytes32 -from lean_spec.spec.ssz.container import Container - - -class AttestationSignatureEntry(NamedTuple): - """ - Single validator's XMSS signature for an attestation. - - Used as an element in the attestation_signatures map: one entry per validator - that attested to the same AttestationData. - """ - - validator_index: ValidatorIndex - signature: Signature - - -class Store[StateT: Container, BlockT: Container](StrictBaseModel): - """ - Forkchoice store tracking chain state and validator attestations. - - This is the "local view" that a node uses to run LMD GHOST. It contains: - - - which blocks and states are known, - - which checkpoints are justified and finalized, - - which block is currently considered the head, - - and, for each validator, their latest attestation that should influence fork choice. - - The `Store` is updated whenever: - - a new block is processed, - - an attestation is received (via a block or gossip), - - an interval tick occurs (activating new attestations), - - or when the head is recomputed. - """ - - time: Interval - """Current time in intervals since genesis.""" - - config: Config - """Chain configuration parameters.""" - - head: Bytes32 - """ - Root of the current canonical chain head block. - - This is the result of running the fork choice algorithm on the current contents of the `Store`. - """ - - safe_target: Bytes32 - """ - Root of the current safe target for attestation. - - This can be used by higher-level logic to restrict which blocks are - considered safe to attest to, based on additional safety conditions. - """ - - latest_justified: Checkpoint - """ - Highest slot justified checkpoint known to the store. - - LMD GHOST starts from this checkpoint when computing the head. - - Only descendants of this checkpoint are considered viable. - """ - - latest_finalized: Checkpoint - """ - Highest slot finalized checkpoint known to the store. - - Everything strictly before this checkpoint can be considered immutable. - - Fork choice will never revert finalized history. - """ - - blocks: dict[Bytes32, BlockT] = Field(default_factory=dict) - """ - Mapping from block root to Block objects. - - This is the set of blocks that the node currently knows about. - - Every block that might participate in fork choice must appear here. - """ - - states: dict[Bytes32, StateT] = Field(default_factory=dict) - """ - Mapping from block root to State objects. - - For each known block, we keep its post-state. - - These states carry justified and finalized checkpoints that we use to update the - `Store`'s latest justified and latest finalized checkpoints. - """ - - validator_index: ValidatorIndex | None - """Index of the validator running this store instance.""" - - attestation_signatures: dict[AttestationData, set[AttestationSignatureEntry]] = Field( - default_factory=dict - ) - """ - Per-validator XMSS signatures learned from committee attesters. - - Keyed by AttestationData. - """ - - latest_new_aggregated_payloads: dict[AttestationData, set[SingleMessageAggregate]] = Field( - default_factory=dict - ) - """ - Aggregated signature proofs pending processing. - - These payloads are "new" and do not yet contribute to fork choice. - They migrate to known payloads via interval ticks. - Populated from gossip aggregated attestations. - Block import does not feed individual proofs into this map directly. - The block-level proof is a merged multi-message aggregate blob verified as a whole. - On gossip-block import, any validator deconstructs that multi-message aggregate into - per-message proofs, writes them back here, and gossips the aggregate. - """ - - latest_known_aggregated_payloads: dict[AttestationData, set[SingleMessageAggregate]] = Field( - default_factory=dict - ) - """ - Aggregated signature proofs that have been processed. - - These payloads are "known" and contribute to fork choice weights. - Used for recursive signature aggregation when building blocks. - """