You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Under Gloas/ePBS, a parent block can be voted on as two distinct LMD variants — FULL (payload delivered timely) or EMPTY (payload not/late) — which fork choice tracks as separate nodes. When Lodestar produces a block, it appears to drop attestations whose head (LMD) vote points at the other payload-status variant than Lodestar's own fork-choice head, even though their FFG (source/target) vote is identical and they are valid to include.
Other clients (Lighthouse, Teku, Prysm) consistently pack these other-variant attestations; Lodestar consistently does not. Net effect is lost attestation inclusion / reduced proposer rewards — not a safety issue (FFG matches, fork choice tracks correctly).
Reported by @nflaig on glamsterdam-devnet-5; investigated together with @twoeths. Filing so we don't lose it — status is "needs further devnet observation" (see below), not a confirmed root cause.
The next-proposer Lighthouse block at slot 38872did include them.
Confirmed on further examples: slots 38890, 41167, 41398 (Teku), and 41307 as an opposite example. At slot 41483 Lodestar did pack them (after correctly reorging two malicious lodestar-ethrex-1 blocks).
"I am pretty sure we should include votes in our blocks even if they do not support our head view (LMD) as we still have same FFG" — @nflaig
"I am just confused why other clients consistently pack these attestations voting on EMPTY while we do not" — @nflaig
Reproduction on the devnet:lodestar-ethrex-1 blocks were "malicious" (voting EMPTY despite a timely payload), which reliably creates the FULL↔EMPTY divergence. Check any lodestar-ethrex-1 block where another Lodestar node is the next proposer, and inspect whether that Lodestar block packs the other-variant attestations.
Expected behavior
Lodestar block production should include valid same-FFG attestations regardless of which payload-status (FULL/EMPTY) LMD variant they vote for, matching Lighthouse/Teku/Prysm. It should not drop otherwise-includable attestations just because their head vote points at the non-canonical-from-our-view variant of the parent.
Suspected mechanism (needs confirmation)
Block-production attestation selection runs through AggregatedAttestationPool.getAttestationsForBlockElectra → getValidateAttestationDataFn → isValidShuffling, which resolves the attestation's beaconBlockRoot via forkChoice.getBlockHexDefaultStatus(beaconBlockRootHex) — i.e. the default-status variant only. Under ePBS the same block root has FULL and EMPTY variants; resolving to the default status can mis-handle attestations that voted for the other variant.
packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts — getAttestationsForBlockElectra (validation via validateAttestationDataFn), getValidateAttestationDataFn, and the getBlockHexDefaultStatus lookup in isValidShuffling.
Related: #9500 ("fix: get attestation block head", @twoeths) selects the correct head-block variant. Per @twoeths it's a precision improvement — "does not look like an issue because we track correctly in forkchoice" — so #9500 alone may not be the fix for the packing gap.
Open questions
Is the omission the LMD-variant filtering above, or the separate known SingleAttestation-not-packed gap (block producer not aggregating same-data SingleAttestations at pack time), or both? These got somewhat conflated during the investigation — e.g. the index-0 attestation in LH block 38872 was actually for slot 38869, and there were only 19 of them.
Does a Lodestar block ever contain both FULL and EMPTY attestations for the previous slot? (Never definitively answered.)
Is it possible no aggregator had produced an aggregate for those attestations beforehand (so nothing was in the pool to pack)?
Needs further observation on the current devnet to re-confirm frequency and to isolate variant-filtering vs SingleAttestation-aggregation as the actual cause(s). Confirmed so far: FFG votes match across variants (so inclusion is valid), fork choice tracks the correct variant, and other clients consistently include these attestations while Lodestar does not.
Additional context
Network: glamsterdam-devnet-5 (ethpandaops). Fork: Glamsterdam / Gloas (ePBS) — the FULL/EMPTY LMD variant split only exists under ePBS.
Origin: ChainSafe/lodestar dev Discord thread "attestation packing issue" (2026-06-09 → 2026-07-05). Participants: @nflaig, @twoeths; cc @wemeetagain.
Summary
Under Gloas/ePBS, a parent block can be voted on as two distinct LMD variants — FULL (payload delivered timely) or EMPTY (payload not/late) — which fork choice tracks as separate nodes. When Lodestar produces a block, it appears to drop attestations whose head (LMD) vote points at the other payload-status variant than Lodestar's own fork-choice head, even though their FFG (source/target) vote is identical and they are valid to include.
Other clients (Lighthouse, Teku, Prysm) consistently pack these other-variant attestations; Lodestar consistently does not. Net effect is lost attestation inclusion / reduced proposer rewards — not a safety issue (FFG matches, fork choice tracks correctly).
Reported by @nflaig on
glamsterdam-devnet-5; investigated together with @twoeths. Filing so we don't lose it — status is "needs further devnet observation" (see below), not a confirmed root cause.Observed behavior
lodestar-ethrex-1blocks).Reproduction on the devnet:
lodestar-ethrex-1blocks were "malicious" (voting EMPTY despite a timely payload), which reliably creates the FULL↔EMPTY divergence. Check anylodestar-ethrex-1block where another Lodestar node is the next proposer, and inspect whether that Lodestar block packs the other-variant attestations.Expected behavior
Lodestar block production should include valid same-FFG attestations regardless of which payload-status (FULL/EMPTY) LMD variant they vote for, matching Lighthouse/Teku/Prysm. It should not drop otherwise-includable attestations just because their head vote points at the non-canonical-from-our-view variant of the parent.
Suspected mechanism (needs confirmation)
Block-production attestation selection runs through
AggregatedAttestationPool.getAttestationsForBlockElectra→getValidateAttestationDataFn→isValidShuffling, which resolves the attestation'sbeaconBlockRootviaforkChoice.getBlockHexDefaultStatus(beaconBlockRootHex)— i.e. the default-status variant only. Under ePBS the same block root has FULL and EMPTY variants; resolving to the default status can mis-handle attestations that voted for the other variant.packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts—getAttestationsForBlockElectra(validation viavalidateAttestationDataFn),getValidateAttestationDataFn, and thegetBlockHexDefaultStatuslookup inisValidShuffling.packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts→aggregatedAttestationPool.getAttestationsForBlock(...).Related: #9500 ("fix: get attestation block head", @twoeths) selects the correct head-block variant. Per @twoeths it's a precision improvement — "does not look like an issue because we track correctly in forkchoice" — so #9500 alone may not be the fix for the packing gap.
Open questions
SingleAttestation-not-packed gap (block producer not aggregating same-dataSingleAttestations at pack time), or both? These got somewhat conflated during the investigation — e.g. the index-0 attestation in LH block 38872 was actually for slot 38869, and there were only 19 of them.Status
Needs further observation on the current devnet to re-confirm frequency and to isolate variant-filtering vs
SingleAttestation-aggregation as the actual cause(s). Confirmed so far: FFG votes match across variants (so inclusion is valid), fork choice tracks the correct variant, and other clients consistently include these attestations while Lodestar does not.Additional context
glamsterdam-devnet-5(ethpandaops). Fork: Glamsterdam / Gloas (ePBS) — the FULL/EMPTY LMD variant split only exists under ePBS.SeenAggregatedAttestationscommittee-index root cause). Different fork, different mechanism.🤖 Filed with AI assistance (lodekeeper) at @nflaig's request, summarizing the Discord investigation thread.