Skip to content

fix: ignore stale finalized payloads in fork choice#820

Open
latifkasuli wants to merge 1 commit into
leanEthereum:mainfrom
latifkasuli:fix/ignore-stale-forkchoice-payloads
Open

fix: ignore stale finalized payloads in fork choice#820
latifkasuli wants to merge 1 commit into
leanEthereum:mainfrom
latifkasuli:fix/ignore-stale-forkchoice-payloads

Conversation

@latifkasuli
Copy link
Copy Markdown
Contributor

@latifkasuli latifkasuli commented Jun 2, 2026

Summary

Fixes a fork-choice vote extraction edge case where a stale aggregated payload targeting an already-finalized block could mask the same validator's still-relevant vote above latest_finalized.

Fork-choice consumers now filter payloads whose target slot is at or below the finalized slot before doing the per-validator latest-message selection. The general extract_attestations_from_aggregated_payloads() decoder remains unchanged so block import, gossip, and pool validation can still inspect payloads before pruning.

Root Cause

Fork choice extracts one latest attestation per validator by comparing AttestationData.slot. If a validator has:

  • a fresh vote above finalization, and
  • a later-slot stale payload whose target is already finalized,

the stale payload previously won the latest-message comparison. Since fork-choice weight accumulation stops at latest_finalized, that selected stale payload contributes no weight above the finalized boundary, effectively hiding the fresh vote.

Validation

  • Pre-fix targeted regression fails with weights == {}
  • uv run pytest tests/lean_spec/spec/forks/lstar/forkchoice/test_compute_block_weights.py -k stale_latest_message -q --no-cov
  • uv run pytest tests/lean_spec/spec/forks/lstar/forkchoice/test_store_attestations.py::test_on_block_processes_multi_validator_aggregations -q --no-cov
  • uv run pytest tests/lean_spec/spec/forks/lstar/forkchoice/test_compute_block_weights.py tests/lean_spec/spec/forks/lstar/forkchoice/test_store_pruning.py tests/lean_spec/spec/forks/lstar/forkchoice/test_attestation_target.py tests/lean_spec/spec/forks/lstar/forkchoice/test_store_attestations.py -q --no-cov
  • uv run --group test fill tests/consensus/lstar/fc/test_store_pruning.py::test_finalization_prunes_stale_aggregated_payloads --fork=Lstar --clean -q
  • uv run pytest tests/lean_spec/spec/forks/lstar/forkchoice/test_validator.py tests/lean_spec/spec/forks/lstar/state/test_state_aggregation.py -q --no-cov
  • uv run --group lint ruff check src/lean_spec/spec/forks/lstar/fork_choice.py tests/lean_spec/spec/forks/lstar/forkchoice/test_compute_block_weights.py
  • uv run --group lint ruff format --check src/lean_spec/spec/forks/lstar/fork_choice.py tests/lean_spec/spec/forks/lstar/forkchoice/test_compute_block_weights.py
  • uv run --group lint ty check src/lean_spec/spec/forks/lstar/fork_choice.py tests/lean_spec/spec/forks/lstar/forkchoice/test_compute_block_weights.py
  • git diff --check

@latifkasuli latifkasuli force-pushed the fix/ignore-stale-forkchoice-payloads branch from 492992f to 02cd39b Compare June 2, 2026 09:27
@latifkasuli latifkasuli force-pushed the fix/ignore-stale-forkchoice-payloads branch from 02cd39b to 94ee5a1 Compare June 2, 2026 10:29
Copy link
Copy Markdown
Collaborator

@tcoratger tcoratger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot!

Please let me know when the PR is ready for review?

I think that you PRs are on draft, please put them as ready for review when you feel they are ready so that someone can have a look :)

Comment on lines +437 to +446
def _fork_choice_payloads(
self,
store: LstarStore,
aggregated_payloads: dict[AttestationData, set[SingleMessageAggregate]],
) -> dict[AttestationData, set[SingleMessageAggregate]]:
return {
attestation_data: proofs
for attestation_data, proofs in aggregated_payloads.items()
if attestation_data.target.slot > store.latest_finalized.slot
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid having a super small function like this in the spec? I think from a readability standpoint (even if this is a bit of code duplication), in the spec context, this is better to just inline the logic at the 3 appropriate locations?

@latifkasuli latifkasuli marked this pull request as ready for review June 2, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants