Skip to content

Commit 76d4792

Browse files
Itodo-Sclaude
andauthored
Add fork choice store pruning test (leanEthereum#604)
* Add fork choice store pruning test * test(fc): add fork choice store pruning test Add a fork choice filler for store pruning on finalization. - Add a devnet fork choice test covering pruning after finalization advances to slot 3 - Verify stale attestation data targeting slots 1, 2, and 3 is removed - Verify attestation data targeting slots 4 and 5 is preserved - Extend StoreChecks with exact target-slot assertions for raw, new, and known attestation pools - Update fork choice attestation steps to retain raw gossip signatures so pruning can be tested end to end Closes leanEthereum#567 Co-Authored-By: Itodo-S <71233924+Itodo-S@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e57e471 commit 76d4792

4 files changed

Lines changed: 406 additions & 1 deletion

File tree

packages/testing/src/consensus_testing/test_fixtures/fork_choice.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ def make_fixture(self) -> Self:
289289
store = store.on_gossip_attestation(
290290
signed_attestation,
291291
scheme=LEAN_ENV_TO_SCHEMES[self.lean_env],
292+
is_aggregator=step.is_aggregator,
292293
)
293294

294295
case GossipAggregatedAttestationStep():

packages/testing/src/consensus_testing/test_types/step_types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,15 @@ class AttestationStep(BaseForkChoiceStep):
149149
The framework fills in the attestation data and signature during make_fixture().
150150
"""
151151

152+
is_aggregator: bool = False
153+
"""
154+
Whether the node holds the aggregator role for this attestation.
155+
156+
Only aggregator nodes store gossip signatures in the raw signature pool.
157+
Defaults to False so existing fillers preserve the behavior where gossip
158+
attestations are validated but not stored.
159+
"""
160+
152161
_filled_attestation: SignedAttestation | None = PrivateAttr(default=None)
153162
"""The filled SignedAttestation, processed through the spec."""
154163

packages/testing/src/consensus_testing/test_types/store_checks.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,30 @@ class StoreChecks(CamelModel):
180180
attestation_checks: list[AttestationCheck] | None = None
181181
"""Optional list of attestation content checks for specific validators."""
182182

183+
attestation_signature_target_slots: list[Slot] | None = None
184+
"""
185+
Expected target slots present in attestation_signatures.
186+
187+
Compares the exact set of target checkpoint slots keyed in the raw gossip
188+
signature map, independent of how many validators signed each target.
189+
"""
190+
191+
latest_new_aggregated_target_slots: list[Slot] | None = None
192+
"""
193+
Expected target slots present in latest_new_aggregated_payloads.
194+
195+
Compares the exact set of target checkpoint slots keyed in the pending
196+
aggregated proof map.
197+
"""
198+
199+
latest_known_aggregated_target_slots: list[Slot] | None = None
200+
"""
201+
Expected target slots present in latest_known_aggregated_payloads.
202+
203+
Compares the exact set of target checkpoint slots keyed in the accepted
204+
aggregated proof map.
205+
"""
206+
183207
block_attestation_count: int | None = None
184208
"""
185209
Expected number of aggregated attestations in the block body.
@@ -351,6 +375,24 @@ def _resolve(label: str) -> Bytes32:
351375
)
352376
check.validate_attestation(extracted[check.validator], label, step_index)
353377

378+
if "attestation_signature_target_slots" in fields:
379+
assert self.attestation_signature_target_slots is not None
380+
actual = sorted({data.target.slot for data in store.attestation_signatures})
381+
expected = sorted(self.attestation_signature_target_slots)
382+
_check("attestation_signatures.target_slots", actual, expected)
383+
384+
if "latest_new_aggregated_target_slots" in fields:
385+
assert self.latest_new_aggregated_target_slots is not None
386+
actual = sorted({data.target.slot for data in store.latest_new_aggregated_payloads})
387+
expected = sorted(self.latest_new_aggregated_target_slots)
388+
_check("latest_new_aggregated_payloads.target_slots", actual, expected)
389+
390+
if "latest_known_aggregated_target_slots" in fields:
391+
assert self.latest_known_aggregated_target_slots is not None
392+
actual = sorted({data.target.slot for data in store.latest_known_aggregated_payloads})
393+
expected = sorted(self.latest_known_aggregated_target_slots)
394+
_check("latest_known_aggregated_payloads.target_slots", actual, expected)
395+
354396
# Block body attestation count
355397
if "block_attestation_count" in fields:
356398
if filled_block is None:

0 commit comments

Comments
 (0)