Skip to content

Commit 5825fbd

Browse files
authored
forkchoice helpers: rm get_latest_justified (leanEthereum#162)
* forkchoice helpers: rm get_latest_justified * clean up
1 parent 32b8294 commit 5825fbd

4 files changed

Lines changed: 23 additions & 92 deletions

File tree

src/lean_spec/subspecs/forkchoice/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@
77

88
from .helpers import (
99
get_fork_choice_head,
10-
get_latest_justified,
1110
)
1211
from .store import Store
1312

1413
__all__ = [
1514
"Store",
1615
"get_fork_choice_head",
17-
"get_latest_justified",
1816
]

src/lean_spec/subspecs/forkchoice/helpers.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44
Pure functions implementing the LMD GHOST forkchoice rule and related utilities.
55
"""
66

7-
from typing import Dict, Optional
7+
from typing import Dict
88

99
from lean_spec.subspecs.containers import (
1010
Block,
11-
Checkpoint,
1211
SignedAttestation,
13-
State,
1412
)
1513
from lean_spec.types import Bytes32, ValidatorIndex
1614

@@ -69,23 +67,3 @@ def get_fork_choice_head(
6967

7068
# Choose best child: most attestations, then lexicographically highest hash
7169
current = max(children, key=lambda x: (attestation_weights.get(x, 0), x))
72-
73-
74-
def get_latest_justified(states: Dict[Bytes32, "State"]) -> Optional[Checkpoint]:
75-
"""
76-
Find the justified checkpoint with the highest slot.
77-
78-
Args:
79-
states: All known states indexed by hash.
80-
81-
Returns:
82-
Latest justified checkpoint, or None if no states.
83-
"""
84-
if not states:
85-
return None
86-
87-
# Find state with maximum justified slot
88-
latest_state = max(states.values(), key=lambda s: s.latest_justified.slot)
89-
90-
# Return latest justified checkpoint from that state
91-
return latest_state.latest_justified

src/lean_spec/subspecs/forkchoice/store.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
)
4444
from lean_spec.types.container import Container
4545

46-
from .helpers import get_fork_choice_head, get_latest_justified
46+
from .helpers import get_fork_choice_head
4747

4848

4949
class Store(Container):
@@ -398,18 +398,27 @@ def update_head(self) -> "Store":
398398
New Store with updated head, latest_justified, and latest_finalized.
399399
400400
"""
401-
# Compute latest justified checkpoint from all known states
401+
# Find the Latest Justified Checkpoint
402402
#
403-
# Scans every state in the store to find the checkpoint with the
404-
# highest slot that has achieved justification.
405-
latest_justified = get_latest_justified(self.states)
406-
407-
# Preserve current justified checkpoint if no newer one found
403+
# We must first determine the anchor point for our fork choice algorithm.
404+
# This anchor is the justified checkpoint (a block root and slot) with the
405+
# highest slot number known across *all* known states.
406+
#
407+
# We find this by:
408+
# a) Scanning all known states.
409+
# b) Finding the state that contains the justified checkpoint with the
410+
# highest slot number.
411+
# c) Extracting that specific checkpoint object to use as our anchor.
408412
#
409-
# This handles the case where no states have updated justification.
410-
# Maintains the previous justified checkpoint rather than losing it.
411-
if latest_justified is None:
412-
latest_justified = self.latest_justified
413+
# If there are no states to scan (e.g., at initialization), the
414+
# operation would fail. In this case, we fall back to using the
415+
# store's currently recorded justified checkpoint, preserving the
416+
# last known good anchor.
417+
latest_justified = (
418+
max(self.states.values(), key=lambda s: s.latest_justified.slot).latest_justified
419+
if self.states
420+
else self.latest_justified
421+
)
413422

414423
# Run LMD-GHOST fork choice algorithm
415424
#

tests/lean_spec/subspecs/forkchoice/test_helpers.py

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
"""Tests for pure forkchoice helper functions."""
22

3-
from typing import TYPE_CHECKING, Dict, Type
3+
from typing import TYPE_CHECKING, Dict
44

55
import pytest
66

7-
from lean_spec.subspecs.containers import Block, BlockBody, Checkpoint, State
7+
from lean_spec.subspecs.containers import Block, BlockBody, Checkpoint
88
from lean_spec.subspecs.containers.block import Attestations
99
from lean_spec.subspecs.containers.slot import Slot
1010
from lean_spec.subspecs.forkchoice.helpers import (
1111
get_fork_choice_head,
12-
get_latest_justified,
1312
)
1413
from lean_spec.subspecs.ssz.hash import hash_tree_root
1514
from lean_spec.types import Bytes32, Uint64, ValidatorIndex
@@ -142,56 +141,3 @@ def test_get_fork_choice_head_multiple_attestations(
142141
)
143142

144143
assert head == target_hash
145-
146-
147-
class TestLatestJustifiedFunction:
148-
"""Test the pure get_latest_justified helper function."""
149-
150-
def test_get_latest_justified_empty(self) -> None:
151-
"""Test get_latest_justified with empty states."""
152-
result = get_latest_justified({})
153-
assert result is None
154-
155-
def test_get_latest_justified_single_state(self, mock_state_factory: Type["MockState"]) -> None:
156-
"""Test get_latest_justified with a single state."""
157-
checkpoint = Checkpoint(root=Bytes32(b"test" + b"\x00" * 28), slot=Slot(5))
158-
159-
states: Dict[Bytes32, State] = {
160-
Bytes32(b"state1" + b"\x00" * 26): mock_state_factory(checkpoint),
161-
}
162-
163-
result = get_latest_justified(states)
164-
assert result == checkpoint
165-
166-
def test_get_latest_justified_multiple_states(
167-
self,
168-
mock_state_factory: Type["MockState"],
169-
) -> None:
170-
"""Test get_latest_justified when states have different slots."""
171-
checkpoint1 = Checkpoint(root=Bytes32(b"test1" + b"\x00" * 27), slot=Slot(10))
172-
checkpoint2 = Checkpoint(
173-
root=Bytes32(b"test2" + b"\x00" * 27), slot=Slot(20)
174-
) # Higher slot
175-
176-
states: Dict[Bytes32, State] = {
177-
Bytes32(b"state1" + b"\x00" * 26): mock_state_factory(checkpoint1),
178-
Bytes32(b"state2" + b"\x00" * 26): mock_state_factory(checkpoint2),
179-
}
180-
181-
result = get_latest_justified(states)
182-
assert result == checkpoint2 # Should return the one with higher slot
183-
184-
def test_get_latest_justified_tie_breaking(self, mock_state_factory: Type["MockState"]) -> None:
185-
"""Test get_latest_justified when slots tie."""
186-
checkpoint1 = Checkpoint(root=Bytes32(b"test1" + b"\x00" * 27), slot=Slot(10))
187-
checkpoint2 = Checkpoint(root=Bytes32(b"test2" + b"\x00" * 27), slot=Slot(10))
188-
189-
states: Dict[Bytes32, State] = {
190-
Bytes32(b"state1" + b"\x00" * 26): mock_state_factory(checkpoint1),
191-
Bytes32(b"state2" + b"\x00" * 26): mock_state_factory(checkpoint2),
192-
}
193-
194-
result = get_latest_justified(states)
195-
# Should return one of them consistently
196-
assert result in [checkpoint1, checkpoint2]
197-
assert result.slot == Slot(10)

0 commit comments

Comments
 (0)