1- """
2- State Container for the Lean Ethereum consensus specification.
3-
4- The state contains everything needed for consensus. It tracks the current slot,
5- recent blocks, and validator attestations. State also records which blocks are
6- justified and finalized.
7- """
1+ """State Container for the Lean Ethereum consensus specification."""
82
93from typing import TYPE_CHECKING , AbstractSet , Iterable
104
@@ -173,7 +167,6 @@ def process_slots(self, target_slot: Slot) -> "State":
173167 #
174168 # 2. Slot Increment:
175169 # It always increments the slot number by one.
176- #
177170 state = state .model_copy (
178171 update = {
179172 "latest_block_header" : (
@@ -223,58 +216,107 @@ def process_block_header(self, block: Block) -> "State":
223216 If any header check fails.
224217 """
225218 # Validation
219+ #
220+ # - Retrieve the header of the previous block (the parent).
221+ # - Compute the parent root hash.
226222 parent_header = self .latest_block_header
227223 parent_root = hash_tree_root (parent_header )
228224
229- # The block must be for the current slot.
225+ # Consensus checks
226+
227+ # Verify the block corresponds to the current state slot.
228+ #
229+ # To move to this slot, we have processed any intermediate slots before.
230230 assert block .slot == self .slot , "Block slot mismatch"
231231
232232 # The block must be newer than the current latest header.
233233 assert block .slot > parent_header .slot , "Block is older than latest header"
234234
235- # The proposer must be the expected validator for this slot.
235+ # Verify the block proposer.
236+ #
237+ # Ensures the block was proposed by the assigned validator for this round.
236238 assert is_proposer (
237239 validator_index = block .proposer_index ,
238240 slot = self .slot ,
239241 num_validators = Uint64 (self .validators .count ),
240242 ), "Incorrect block proposer"
241243
242- # The declared parent must match the hash of the latest block header.
244+ # Verify the chain link.
245+ #
246+ # The block must cryptographically point to the known parent.
243247 assert block .parent_root == parent_root , "Block parent root mismatch"
244248
245- # State Updates
249+ # Checkpoint Updates
246250
247- # Special case: first block after genesis.
251+ # Detect if we are transitioning from the genesis block.
252+ #
253+ # This flag is True only when processing the very first block of the chain.
254+ # This means the parent is the Genesis block (Slot 0).
248255 is_genesis_parent = parent_header .slot == Slot (0 )
249- new_latest_justified = (
250- self .latest_justified .model_copy (update = {"root" : parent_root })
251- if is_genesis_parent
252- else self .latest_justified
253- )
254- new_latest_finalized = (
255- self .latest_finalized .model_copy (update = {"root" : parent_root })
256- if is_genesis_parent
257- else self .latest_finalized
258- )
259256
260- # If there were empty slots between parent and this block, fill them.
257+ # Update the consensus checkpoints.
258+ #
259+ # This logic acts as the trust anchor for the chain:
260+ #
261+ # - If the parent is the Genesis block: It cannot receive votes as it
262+ # precedes the start of the chain. Therefore, we explicitly force it
263+ # to be Justified and Finalized immediately.
264+ #
265+ # - For all other blocks: We retain the existing checkpoints. Future
266+ # updates rely entirely on validator attestations which are processed
267+ # later in the block body.
268+ if is_genesis_parent :
269+ new_latest_justified = self .latest_justified .model_copy (update = {"root" : parent_root })
270+ new_latest_finalized = self .latest_finalized .model_copy (update = {"root" : parent_root })
271+ else :
272+ new_latest_justified = self .latest_justified
273+ new_latest_finalized = self .latest_finalized
274+
275+ # Historical Data Management
276+
277+ # Calculate the gap between the parent and the current block.
278+ #
279+ # If slots were skipped (missed proposals), we must record them.
280+ #
281+ # Formula: (Current - Parent - 1). Adjacent blocks have a gap of 0.
261282 num_empty_slots = (block .slot - parent_header .slot - Slot (1 )).as_int ()
262283
263- # Build new historical hashes list
284+ # Update the list of historical block roots.
285+ #
286+ # Structure: [Existing history] + [Parent root] + [Zero hash for gaps]
264287 new_historical_hashes_data = (
265- self .historical_block_hashes + [parent_root ] + ( [ZERO_HASH ] * num_empty_slots )
288+ self .historical_block_hashes + [parent_root ] + [ZERO_HASH ] * num_empty_slots
266289 )
267290
268- # Build new justified slots list
291+ # Update the list of justified slot flags.
292+ #
293+ # Structure: [Existing flags] + [Is genesis parent?] + [False for gaps]
294+ #
295+ # We construct the new history list by concatenating three segments:
296+ #
297+ # 1. The existing history:
298+ # We preserve the flags for all previously processed blocks.
299+ #
300+ # 2. The parent block status (one entry):
301+ # We append the status of the block immediately preceding any gaps.
302+ # - If Genesis: True (Justified by definition).
303+ # - If Normal: False (Pending). It remains unjustified until validators
304+ # vote for it later in the process.
305+ #
306+ # 3. The skipped slots status (multiple entries):
307+ # We append False for every empty slot between the parent and the
308+ # current block. Since no blocks exist there, they are permanently
309+ # unjustified.
269310 new_justified_slots_data = (
270- self .justified_slots
271- + [Boolean (is_genesis_parent )]
272- + ([Boolean (False )] * num_empty_slots )
311+ self .justified_slots + [Boolean (is_genesis_parent )] + [Boolean (False )] * num_empty_slots
273312 )
274313
275314 # Construct the new latest block header.
276315 #
277- # Leave state_root empty; it will be filled on the next process_slot call.
316+ # The new header object represents the tip of the chain.
317+ #
318+ # Leave state root empty.
319+ # It is not computed until the block body is fully processed or the next slot begins.
278320 new_header = BlockHeader (
279321 slot = block .slot ,
280322 proposer_index = block .proposer_index ,
@@ -285,15 +327,14 @@ def process_block_header(self, block: Block) -> "State":
285327
286328 # Final Immutable Copy
287329 #
288- # Return the state with all header updates applied in one go.
330+ # Return a new immutable state instance.
331+ # All calculated updates are applied atomically here.
289332 return self .model_copy (
290333 update = {
291334 "latest_justified" : new_latest_justified ,
292335 "latest_finalized" : new_latest_finalized ,
293- "historical_block_hashes" : self .historical_block_hashes .__class__ (
294- data = new_historical_hashes_data
295- ),
296- "justified_slots" : self .justified_slots .__class__ (data = new_justified_slots_data ),
336+ "historical_block_hashes" : HistoricalBlockHashes (data = new_historical_hashes_data ),
337+ "justified_slots" : JustifiedSlots (data = new_justified_slots_data ),
297338 "latest_block_header" : new_header ,
298339 }
299340 )
@@ -472,7 +513,7 @@ def process_attestations(
472513 update = {
473514 "justifications_roots" : justifications_roots ,
474515 "justifications_validators" : justifications_validators ,
475- "justified_slots" : self . justified_slots . __class__ (data = justified_slots ),
516+ "justified_slots" : JustifiedSlots (data = justified_slots ),
476517 "latest_justified" : latest_justified ,
477518 "latest_finalized" : latest_finalized ,
478519 }
0 commit comments