Add fork choice test for split LMD votes across payload variants#5345
Draft
barnabasbusa wants to merge 5 commits into
Draft
Add fork choice test for split LMD votes across payload variants#5345barnabasbusa wants to merge 5 commits into
barnabasbusa wants to merge 5 commits into
Conversation
Adds a Gloas fork_choice trace in which two disjoint beacon committees attest to the same beacon_block_root with opposite `payload_index` values, so the same root receives non-zero LMD weight on both `PAYLOAD_STATUS_FULL` and `PAYLOAD_STATUS_EMPTY` viable fork-choice nodes simultaneously. The pyspec passes by construction because `get_attestation_score` recomputes per-node weight as a stateless sum over `store.latest_messages` (via `get_supported_node`). Incremental fork-choice implementations that maintain running per-node weights must produce the same `viable_for_head_roots_and_weights` values emitted in the generated trace; this is the surface where weight accounting bugs (e.g. uint64 underflow, "attesting balance should never go below zero") for the new FULL/EMPTY axis surface.
jtraglia
reviewed
Jun 8, 2026
Address review feedback: rephrase docstring to describe the incremental weight optimisation abstractly without naming specific client implementations.
brech1
reviewed
Jun 10, 2026
Address review feedback on the split-payload LMD test: replace the local init block with setup_one_block_store, drop the _advance_store_to_slot wrapper in favour of tick_store_to_slot, and assert get_attestation_score returns the pre-calculated weight per payload variant rather than just checking sign.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Gloas fork_choice trace in which two disjoint beacon committees attest to the same
beacon_block_rootwith oppositepayload_indexvalues, so the same root ends up with non-zero LMD weight on bothPAYLOAD_STATUS_FULLandPAYLOAD_STATUS_EMPTYviable fork-choice nodes at the same time.tests/core/pyspec/eth_consensus_specs/test/gloas/fork_choice/test_split_payload_status_lmd_votes.pytest_lmd_vote_split_across_payload_status_variantsreftests/{minimal,mainnet}/gloas/fork_choice/split_payload_status_lmd_votes/.Why
Under EIP-7732 the same
LatestMessage.rootresolves throughget_supported_nodeto two distinctForkChoiceNodevalues depending onLatestMessage.payload_present:ForkChoiceNode(root=R, payload_status=PAYLOAD_STATUS_FULL)ForkChoiceNode(root=R, payload_status=PAYLOAD_STATUS_EMPTY)get_weight/get_attestation_scorein the spec recompute the score from scratch each call by summingeffective_balanceoverstore.latest_messages, so the spec never adds or subtracts a running weight and stays correct by construction.Production clients (e.g. Teku's ProtoArray, Grandine's
fork_choice_store) maintain running per-node weights and must independently track two totals for the same root once Gloas activates. There is currently no Gloas fork_choice test that places non-zero weight on both payload variants of the same root in a single store, so the test suite cannot distinguish an implementation that correctly maintains the two running totals from one that conflates or double-counts them.On a recent Glamsterdam devnet (
glamsterdam-devnet-5), the absence of such coverage coincided with two independent fork-choice ERROR-level signatures around exactly this surface:This PR does not claim to reproduce either client bug exactly. It adds a minimal, spec-faithful scenario that emits the per-variant weights an incremental implementation must match.
What the test does
Trace, on top of the Gloas anchor (minimal + mainnet presets):
block_1at slot 1 and reveal its execution payload envelope, so(block_1, FULL)is a viable fork-choice node.beacon_block_root = block_1withpayload_index = 1(payload_present = True).beacon_block_root = block_1withpayload_index = 0(payload_present = False).The test then asserts:
LatestMessage.payload_present is True(slot 2 stamp).LatestMessage.payload_present is False(slot 3 stamp).payload_present = False(the laterslot > store.latest_messages[i].slotrule fromupdate_latest_messages).get_attestation_scoreis> 0for(block_1, EMPTY), and> 0for(block_1, FULL)whenever at least one slot-2 voter did not re-attest at slot 3.full_score + empty_score <= sum(effective_balance for v in full ∪ empty)— i.e. no validator's balance is double-counted across the two variants.output_store_checks(..., with_viable_for_head_weights=True)emitsviable_for_head_roots_and_weightsinsteps.yaml, which lists the two(block_1, FULL)and(block_1, EMPTY)leaves with their weights — the exact values incremental implementations must reproduce.Generated
steps.yamltail (minimal preset):Test plan
make test k="lmd_vote_split_across_payload_status_variants" fork=gloas— passes (2 passed across minimal+mainnet, 1 skipped on general preset).make test k="lmd_vote_split_across_payload_status_variants" fork=gloas reftests=true— fixtures generated underreftests/minimal/gloas/fork_choice/split_payload_status_lmd_votes/andreftests/mainnet/gloas/fork_choice/split_payload_status_lmd_votes/.make lint— clean.Notes for reviewers
@with_gloas_and_later), consistent with the other files ingloas/fork_choice/.payload_timelinessandpayload_data_availabilitycomparisons #5331 / Clarifyshould_extend_payload's note and add a slot assert #5336 / Removeshould_extend_payloadassertions from payload tests #5338-style cleanups aroundshould_extend_payload/payload_timelinessif that's the preferred grouping.