1- """Consensus layer pre- state generation ."""
1+ """Consensus layer genesis state, block, and anchor construction for tests ."""
22
33from consensus_testing .keys import XmssKeyManager
44from lean_spec .spec .crypto .merkleization import hash_tree_root
1818from lean_spec .spec .forks .lstar .spec import LstarSpec
1919from lean_spec .spec .ssz import Bytes52 , Uint64
2020
21- _DEFAULT_GENESIS_TIME = Uint64 (0 )
2221
23- _DEFAULT_VALIDATOR_INDEX = ValidatorIndex (0 )
24- """Owning validator for a genesis store, unless overridden."""
25-
26-
27- def _build_validators (num_validators : int ) -> Validators :
28- """Build a validator registry with real XMSS keys from the shared key manager."""
29- key_manager = XmssKeyManager .shared ()
22+ def build_genesis_state (
23+ num_validators : int = 4 ,
24+ * ,
25+ genesis_time : Uint64 = Uint64 (0 ),
26+ keyed : bool = True ,
27+ fork : LstarSpec = LstarSpec (),
28+ ) -> State :
29+ """
30+ Build a genesis pre-state for consensus tests.
3031
31- if num_validators > len (key_manager ):
32- raise ValueError (
33- f"Not enough keys: need { num_validators } validators "
34- f"but the key manager has only { len (key_manager )} keys"
35- )
32+ Keyed validators get real signing keys from the shared key manager.
33+ Unkeyed validators get zeroed keys, for tests that never check signatures.
3634
37- validators = []
38- for validator_position in range (num_validators ):
39- validator_index = ValidatorIndex (validator_position )
40- attestation_public_key , proposal_public_key = key_manager .get_public_keys (validator_index )
41- validators .append (
35+ Raises:
36+ ValueError: If keyed and the key manager holds fewer keys than requested.
37+ """
38+ if keyed :
39+ key_manager = XmssKeyManager .shared ()
40+ if num_validators > len (key_manager ):
41+ raise ValueError (
42+ f"Not enough keys: need { num_validators } validators "
43+ f"but the key manager has only { len (key_manager )} keys"
44+ )
45+ validators = []
46+ for validator_position in range (num_validators ):
47+ validator_index = ValidatorIndex (validator_position )
48+ attestation_public_key , proposal_public_key = key_manager .get_public_keys (
49+ validator_index
50+ )
51+ validators .append (
52+ Validator (
53+ attestation_public_key = Bytes52 (attestation_public_key .encode_bytes ()),
54+ proposal_public_key = Bytes52 (proposal_public_key .encode_bytes ()),
55+ index = validator_index ,
56+ )
57+ )
58+ else :
59+ validators = [
4260 Validator (
43- attestation_public_key = Bytes52 (attestation_public_key . encode_bytes () ),
44- proposal_public_key = Bytes52 (proposal_public_key . encode_bytes () ),
45- index = validator_index ,
61+ attestation_public_key = Bytes52 (b" \x00 " * 52 ),
62+ proposal_public_key = Bytes52 (b" \x00 " * 52 ),
63+ index = ValidatorIndex ( validator_position ) ,
4664 )
47- )
65+ for validator_position in range (num_validators )
66+ ]
4867
49- return Validators (data = validators )
68+ return fork .generate_genesis (
69+ genesis_time = genesis_time ,
70+ validators = Validators (data = validators ),
71+ )
5072
5173
52- def generate_pre_state (
53- fork : LstarSpec | None = None ,
54- genesis_time : Uint64 = _DEFAULT_GENESIS_TIME ,
55- num_validators : int = 4 ,
56- ) -> State :
74+ def reconstruct_block_from_header (state : State ) -> Block :
5775 """
58- Generate a default pre-state for consensus tests.
59-
60- Args:
61- fork: Fork dispatching genesis construction. Defaults to a fresh
62- LstarSpec instance.
63- genesis_time: The genesis timestamp.
64- num_validators: Number of validators to include.
76+ Rebuild the block matching a state's latest header.
6577
66- Returns:
67- A properly initialized consensus state .
78+ The body is empty by the genesis and empty-block convention.
79+ For a genesis state this is the genesis block .
6880 """
69- fork = fork or LstarSpec ()
70- validators = _build_validators (num_validators )
71- return fork .generate_genesis (genesis_time = genesis_time , validators = validators )
81+ return Block (
82+ slot = state .latest_block_header .slot ,
83+ proposer_index = state .latest_block_header .proposer_index ,
84+ parent_root = state .latest_block_header .parent_root ,
85+ state_root = hash_tree_root (state ),
86+ body = BlockBody (attestations = AggregatedAttestations (data = [])),
87+ )
7288
7389
7490def build_anchor (
7591 num_validators : int ,
7692 anchor_slot : Slot ,
77- fork : LstarSpec | None = None ,
78- genesis_time : Uint64 = _DEFAULT_GENESIS_TIME ,
93+ * ,
94+ fork : LstarSpec = LstarSpec (),
95+ genesis_time : Uint64 = Uint64 (0 ),
96+ keyed : bool = True ,
7997 synced : bool = False ,
8098) -> tuple [State , Block ]:
8199 """
82- Build a non-genesis anchor by advancing the genesis state to a slot.
83-
84- By default the anchor keeps the genesis checkpoints, modelling a mid-chain
85- state that has not finalized anything yet.
100+ Build an anchor by advancing the genesis state through a slot.
86101
87- With synced set, it models a checkpoint-synced node instead: both checkpoints
88- pin to the anchor slot and the justification window rebases onto that boundary.
89-
90- Either way the returned pair is internally consistent.
91- The block state root equals the hash of the state.
92-
93- Args:
94- num_validators: Size of the validator set in the anchor state.
95- anchor_slot: Slot at which the anchor block lives. Must be > 0.
96- fork: Fork dispatching genesis construction.
97- genesis_time: Genesis timestamp for the underlying pre-state.
98- synced: Pin both checkpoints to the anchor slot, for checkpoint-sync vectors.
99-
100- Returns:
101- A tuple of (anchor_state, anchor_block).
102-
103- Raises:
104- ValueError: If anchor_slot is not strictly positive.
102+ At slot 0 the advance loop is empty, so this returns the genesis pair.
105103 """
106- if anchor_slot <= Slot (0 ):
107- raise ValueError (
108- f"Anchor slot must be strictly positive, got { anchor_slot } . "
109- "For a genesis anchor use generate_pre_state instead."
110- )
111-
112- fork = fork or LstarSpec ()
113- state = generate_pre_state (fork = fork , genesis_time = genesis_time , num_validators = num_validators )
104+ state = build_genesis_state (num_validators , genesis_time = genesis_time , keyed = keyed , fork = fork )
114105
115- # Reconstruct the genesis block from the state's latest header.
116- # The genesis block is fully determined by the genesis state.
117106 current_block = reconstruct_block_from_header (state )
118107 parent_root = hash_tree_root (current_block )
119108
120109 num_validators_u64 = Uint64 (num_validators )
121110
122- # Advance through empty blocks, one per slot, up to and including anchor_slot.
123- # Each block is built by the spec's own builder so the resulting state
124- # carries the real chain history (historical block hashes, justified slots,
125- # justification tracking) that a real mid-chain state would have.
111+ # Advance one empty block per slot, up to and including the anchor.
112+ # Using the spec's own builder gives the state real mid-chain history.
126113 for next_slot in range (1 , int (anchor_slot ) + 1 ):
127114 slot = Slot (next_slot )
128115 proposer_index = ValidatorIndex .proposer_for_slot (slot , num_validators_u64 )
@@ -139,19 +126,13 @@ def build_anchor(
139126 if not synced :
140127 return state , current_block
141128
142- # Rebase the state onto the anchor as a freshly checkpoint-synced node would see it.
143- #
144- # The empty-block advance leaves both checkpoints at the genesis boundary (slot 0).
145- # A node that syncs from this anchor trusts it as finalized at the anchor slot.
146- # So both checkpoints move to the anchor block at the anchor slot.
129+ # Rebase the state as a freshly checkpoint-synced node would see it.
130+ # Such a node trusts the anchor as finalized, so both checkpoints move there.
147131 anchor_root = hash_tree_root (current_block )
148132 anchor_checkpoint = Checkpoint (root = anchor_root , slot = anchor_slot )
149133
150- # The justified-slots window is stored relative to the finalized boundary.
151- #
152- # Its first bit is the slot just after finalization.
153- # Moving the boundary forward by the anchor slot drops that many leading bits.
154- # No slot beyond the anchor is materialized, so the rebased window is empty.
134+ # The justified-slots window starts at the slot after the finalized boundary.
135+ # Moving that boundary to the anchor drops the leading bits; nothing past it exists.
155136 rebase_distance = int (anchor_slot - state .latest_finalized .slot )
156137 rebased_justified_slots = JustifiedSlots (data = state .justified_slots .data [rebase_distance :])
157138
@@ -172,70 +153,27 @@ def build_anchor(
172153 return state , current_block
173154
174155
175- def make_validators (count : int ) -> Validators :
176- """Build a validator registry of the given size with zeroed public keys."""
177- return Validators (
178- data = [
179- Validator (
180- attestation_public_key = Bytes52 (b"\x00 " * 52 ),
181- proposal_public_key = Bytes52 (b"\x00 " * 52 ),
182- index = ValidatorIndex (validator_position ),
183- )
184- for validator_position in range (count )
185- ]
186- )
187-
188-
189- def make_genesis_state (num_validators : int = 3 , genesis_time : int = 0 ) -> State :
190- """Build a genesis state with zeroed validator keys."""
191- return LstarSpec ().generate_genesis (
192- genesis_time = Uint64 (genesis_time ),
193- validators = make_validators (num_validators ),
194- )
195-
196-
197- def reconstruct_block_from_header (state : State ) -> Block :
198- """
199- Rebuild the block matching a state's latest header.
200-
201- The header pins the slot, proposer index, and parent root.
202- The state root is the hash of the state itself.
203- The body is the empty body of the genesis and empty-block convention.
204-
205- For a genesis state this is the genesis block.
206- """
207- return Block (
208- slot = state .latest_block_header .slot ,
209- proposer_index = state .latest_block_header .proposer_index ,
210- parent_root = state .latest_block_header .parent_root ,
211- state_root = hash_tree_root (state ),
212- body = BlockBody (attestations = AggregatedAttestations (data = [])),
213- )
214-
215-
216- def make_genesis_store (
156+ def build_genesis_store (
217157 num_validators : int = 4 ,
218158 * ,
219159 genesis_time : int = 0 ,
220- validator_index : ValidatorIndex | None = _DEFAULT_VALIDATOR_INDEX ,
160+ validator_index : ValidatorIndex | None = ValidatorIndex ( 0 ) ,
221161 observer : bool = False ,
222162 keyed : bool = True ,
223163 time : Interval | None = None ,
224164) -> Store :
225165 """
226166 Build a genesis fork-choice store.
227167
228- Uses real XMSS keys when keyed, else zeroed keys for any validator count.
229168 Set observer for a store with no owning validator.
230169 """
231- state = (
232- generate_pre_state (genesis_time = Uint64 (genesis_time ), num_validators = num_validators )
233- if keyed
234- else make_genesis_state (num_validators = num_validators , genesis_time = genesis_time )
170+ # Slot 0 makes the anchor builder produce the genesis state and block pair.
171+ state , genesis_block = build_anchor (
172+ num_validators , Slot (0 ), genesis_time = Uint64 (genesis_time ), keyed = keyed
235173 )
236174 store = LstarSpec ().create_store (
237175 state ,
238- reconstruct_block_from_header ( state ) ,
176+ genesis_block ,
239177 validator_index = None if observer else validator_index ,
240178 )
241179 return store if time is None else store .model_copy (update = {"time" : time })
0 commit comments