1212]
1313
1414import copy
15+ from collections import defaultdict
1516from typing import Dict
1617
1718from lean_spec .subspecs .chain .config import (
@@ -491,15 +492,23 @@ def _compute_lmd_ghost_head(
491492 min_score : int = 0 ,
492493 ) -> Bytes32 :
493494 """
494- Internal implementation of LMD GHOST fork choice algorithm .
495+ Walk the block tree according to the LMD GHOST rule .
495496
496- Navigates the block tree from `start_root` by choosing the heaviest child
497- at each fork, based on the provided `attestations`.
497+ The walk starts from a chosen root.
498+ At each fork, the child subtree with the highest weight is taken.
499+ The process stops when a leaf is reached.
500+ That leaf is the chosen head.
498501
499- This is the core fork choice logic. It walks down the tree from a given
500- starting point (typically the latest justified checkpoint), choosing at
501- each fork the child with the most attestation weight. When there is a tie,
502- it breaks it lexicographically by hash.
502+ Weights are derived from votes as follows:
503+ - Each validator contributes its full weight to its most recent head vote.
504+ - The weight of that vote also flows to every ancestor of the voted block.
505+ - The weight of a subtree is the sum of all such contributions inside it.
506+
507+ An optional threshold can be applied:
508+ - If a threshold is set, children below this threshold are ignored.
509+
510+ When two branches have equal weight, the one with the lexicographically
511+ larger hash is chosen to break ties.
503512
504513 Args:
505514 start_root: Starting point root (usually latest justified).
@@ -509,43 +518,64 @@ def _compute_lmd_ghost_head(
509518 Returns:
510519 Hash of the chosen head block.
511520 """
512- # Start at genesis if root is zero hash
521+ # If the starting point is not defined, choose the earliest known block.
522+ #
523+ # This ensures that the walk always has an anchor.
513524 if start_root == ZERO_HASH :
514525 start_root = min (
515526 self .blocks .keys (), key = lambda block_hash : self .blocks [block_hash ].slot
516527 )
517528
518- # Count attestations for each block (attestations for descendants count for ancestors)
519- attestation_weights : Dict [Bytes32 , int ] = {}
529+ # Remember the slot of the anchor once and reuse it during the walk.
530+ #
531+ # This avoids repeated lookups inside the inner loop.
532+ start_slot = self .blocks [start_root ].slot
520533
534+ # Prepare a table that will collect voting weight for each block.
535+ #
536+ # Each entry starts conceptually at zero and then accumulates contributions.
537+ weights : Dict [Bytes32 , int ] = defaultdict (int )
538+
539+ # For every vote, follow the chosen head upward through its ancestors.
540+ #
541+ # Each visited block accumulates one unit of weight from that validator.
521542 for attestation in attestations .values ():
522- head = attestation .message .data .head
523- if head .root in self .blocks :
524- # Walk up from attestation target, incrementing ancestor weights
525- block_hash = head .root
526- while self .blocks [block_hash ].slot > self .blocks [start_root ].slot :
527- attestation_weights [block_hash ] = attestation_weights .get (block_hash , 0 ) + 1
528- block_hash = self .blocks [block_hash ].parent_root
529-
530- # Build children mapping for ALL blocks (not just those above min_score)
543+ current_root = attestation .message .data .head .root
544+
545+ # Climb towards the anchor while staying inside the known tree.
546+ #
547+ # This naturally handles partial views and ongoing sync.
548+ while current_root in self .blocks and self .blocks [current_root ].slot > start_slot :
549+ weights [current_root ] += 1
550+ current_root = self .blocks [current_root ].parent_root
551+
552+ # Build the adjacency tree (parent -> children).
531553 #
532- # This ensures fork choice works even when there are no attestations
533- children_map : Dict [Bytes32 , list [Bytes32 ]] = {}
534- for block_hash , block in self .blocks .items ():
535- if block .parent_root :
536- # Only include blocks that have enough attestations OR when min_score is 0
537- if min_score == 0 or attestation_weights .get (block_hash , 0 ) >= min_score :
538- children_map .setdefault (block .parent_root , []).append (block_hash )
539-
540- # Walk down tree, choosing child with most attestations (tiebreak by lexicographic hash)
541- current = start_root
542- while True :
543- children = children_map .get (current , [])
544- if not children :
545- return current
554+ # We use a defaultdict to avoid checking if keys exist.
555+ children_map : Dict [Bytes32 , list [Bytes32 ]] = defaultdict (list )
556+
557+ for root , block in self .blocks .items ():
558+ # 1. Structural check: skip blocks without parents (e.g., purely genesis/orphans)
559+ if not block .parent_root :
560+ continue
561+
562+ # 2. Heuristic check: prune branches early if they lack sufficient weight
563+ if min_score > 0 and weights [root ] < min_score :
564+ continue
546565
566+ children_map [block .parent_root ].append (root )
567+
568+ # Now perform the greedy walk.
569+ #
570+ # At each step, pick the child with the highest weight among the candidates.
571+ head = start_root
572+
573+ # Descend the tree, choosing the heaviest branch at every fork.
574+ while children := children_map .get (head ):
547575 # Choose best child: most attestations, then lexicographically highest hash
548- current = max (children , key = lambda x : (attestation_weights .get (x , 0 ), x ))
576+ head = max (children , key = lambda x : (weights [x ], x ))
577+
578+ return head
549579
550580 def update_head (self ) -> "Store" :
551581 """
0 commit comments