# VERDICT CONFIRMED — Grandine; conditional in Lodestar; Nimbus trigger not established

At the pinned Gloas [EIP-7732 fork-choice spec](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/gloas/fork-choice.md#L807-L833) ([EIP text at the supplied pin](https://raw.githubusercontent.com/ethereum/EIPs/d4c57552/EIPS/eip-7732.md)), `is_head_weak` starts from the **PENDING** node's justified-state attestation score, adds each equivocating head-committee member's **justified-state effective balance**, then tests `head_weight < reorg_threshold`. Grandine instead adds its zeroed active-balance cache for a validator already slashed in the justified state. That validator can still be in a head-slot committee and have positive effective balance: [slashing sets `slashed` and initiates a delayed exit, but does not zero `effective_balance`](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/beacon-chain.md#L1673-L1721). The discrepancy can cross the threshold and change whether [proposer boost is withheld after an early sibling equivocation](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/gloas/fork-choice.md#L531-L561). Lodestar's different fallback is a deviation only when the justified and head effective balances differ. Nimbus's apparent FULL omission cannot affect this particular previous-slot-parent decision under the spec's attestation timing rule; a separate stale-boost concern is not proved here. This is a **fork-choice divergence**, not an invalid-block claim.

## Strongest refutation attempt

1. **Spec meaning and same-path correction.** The pinned [phase0 `get_attestation_score`](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/fork-choice.md#L335-L357) excludes slashed validators from ordinary votes, but Gloas's extra equivocation sum explicitly reads `justified_state.validators[index].effective_balance` without an active/unslashed filter ([Gloas lines 815–832](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/gloas/fork-choice.md#L815-L832)). The [threshold calculation](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/fork-choice.md#L297-L307) *does* exclude slashed validators, matching Grandine's denominator; it does not cancel the missing numerator. Grandine's caller uses the result directly ([lines 1630–1667](https://raw.githubusercontent.com/grandinetech/grandine/52cd677117b5d93bf477fe4843148070b0303444/fork_choice_store/src/store.rs#L1630-L1667)) and reaches that caller in post-Gloas head selection ([lines 6206–6216](https://raw.githubusercontent.com/grandinetech/grandine/52cd677117b5d93bf477fe4843148070b0303444/fork_choice_store/src/store.rs#L6206-L6216)). No compensating check appears on that path.
2. **Valid-chain invariant.** The [active predicate](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/beacon-chain.md#L1088-L1096) depends on activation and exit epochs, not the slashed flag. [`slash_validator` preserves effective balance and schedules exit](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/beacon-chain.md#L1700-L1721), and the [exit epoch is delayed](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/beacon-chain.md#L1353-L1361). Thus a slashed validator with positive effective balance can still be assigned to the head-slot committee; [`on_attester_slashing` adds its index to `equivocating_indices`](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/fork-choice.md#L1035-L1055). This refutes the suggested zero-effective-balance invariant for Grandine.
3. **Threshold bound.** In a valid illustrative state with 1,000 active 32 ETH validators, one slashed but not yet exited, the [20% and 32-slots parameters](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/configs/mainnet.yaml#L154-L156) ([slots preset](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/presets/mainnet/phase0.yaml#L34)) give a 199.8 ETH threshold from the 999 unslashed validators. Six 32 ETH ordinary votes yield 192 ETH; one 32 ETH equivocating committee member yields 224 ETH by spec, but 192 ETH in Grandine. Hence `is_head_weak` flips from **false** to **true**. With a previously observed PTC-timely sibling by the same parent-slot proposer, [the spec boost rule](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/gloas/fork-choice.md#L531-L561) applies boost while Grandine withholds it. The 32 ETH difference exceeds the 7.8 ETH gap; this is a boundary construction, not a measured devnet event.
4. **Later fix.** The specified Grandine [develop branch head](https://api.github.com/repos/grandinetech/grandine/branches/develop), Lodestar [unstable branch head](https://api.github.com/repos/ChainSafe/lodestar/branches/unstable), and Nimbus [stable branch head](https://api.github.com/repos/status-im/nimbus-eth2/branches/stable) were respectively the supplied pins `52cd677…`, `e4b4f615…`, and `0abc0dd0…` when checked; therefore no later commit on those named branches repaired these pinned lines. Grandine's [file commit history](https://api.github.com/repos/grandinetech/grandine/commits?path=fork_choice_store/src/store.rs) was also checked. Other branches and future merges are not covered.
5. **Nimbus's strongest apparent difference.** Nimbus reads `proto_node.weight` and subtracts only boost when the queried root itself equals `previousProposerBoostRoot` ([`fork_choice_epbs.nim` lines 130–158](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/fork_choice_epbs.nim#L130-L158)). Its FULL and base nodes have separate weights, with a distinct helper that sums the variants ([`proto_array.nim` lines 616–631](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/proto_array.nim#L616-L631)); `is_head_weak` does not call that helper. But [`should_apply_proposer_boost` only calls it for a previous-slot parent](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/fork_choice_epbs.nim#L161-L199). A FULL vote for that parent needs an attestation from a later slot ([Gloas `get_supported_node`](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/gloas/fork-choice.md#L431-L451)), and [that vote cannot affect fork choice until its slot has passed](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/gloas/fork-choice.md#L937-L954); Nimbus [queues votes under that rule](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/fork_choice.nim#L149-L166). Therefore the omitted FULL weight is **zero at this call** on a valid path. A previously applied boost on the parent itself is subtracted; a boosted *child* could contribute up to the 40% proposer score through [proto-array ancestor propagation](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/proto_array.nim#L281-L343), which is arithmetically above the 20% weak threshold, but the supplied early-sibling trigger was not shown to make the decision flip after that first application: Nimbus records PTC-timely sibling blocks only in their own slot ([lines 447–463](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/fork_choice.nim#L447-L463)). Its production `get_head` does reach this check ([lines 643–656](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/fork_choice.nim#L643-L656)). **Nimbus subclaim: unproved for the stated condition**, not used to establish the verdict.

## Corrected record

**Pinned spec quote:** `head_node = ForkChoiceNode(root=head_root, payload_status=PAYLOAD_STATUS_PENDING)`; `justified_state.validators[index].effective_balance`; `return head_weight < reorg_threshold` ([Gloas `fork-choice.md#L819-L832`](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/gloas/fork-choice.md#L819-L832)).

| Client | Pinned code and difference | Trigger / bound | Assessment |
|---|---|---|---|
| **Grandine** | [`fork_choice_store/src/store.rs#L1501-L1526`](https://raw.githubusercontent.com/grandinetech/grandine/52cd677117b5d93bf477fe4843148070b0303444/fork_choice_store/src/store.rs#L1501-L1526) adds `justified_active_balances`, which [`active_balances` zeroes when slashed or inactive](https://raw.githubusercontent.com/grandinetech/grandine/52cd677117b5d93bf477fe4843148070b0303444/fork_choice_store/src/store.rs#L6328-L6344). Spec adds the justified effective balance; Grandine's [threshold](https://raw.githubusercontent.com/grandinetech/grandine/52cd677117b5d93bf477fe4843148070b0303444/fork_choice_store/src/store.rs#L1463-L1475) already matches the spec's unslashed denominator. | A slashed, still-active equivocator in the parent-slot committee; ordinary votes just below the threshold; PTC-timely same-proposer sibling. Difference up to that equivocator's positive effective balance; explicit flip above. | **High confidence; fork-choice divergence.** [Teku at its pin](https://raw.githubusercontent.com/Consensys/teku/2c35574b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/util/ForkChoiceUtilGloas.java#L463-L530) uses justified effective balance and PENDING attestation weight. |
| **Lodestar** | [`packages/fork-choice/src/forkChoice/forkChoice.ts#L1717-L1754`](https://raw.githubusercontent.com/ChainSafe/lodestar/e4b4f615/packages/fork-choice/src/forkChoice/forkChoice.ts#L1717-L1754) falls back from a zero/absent justified balance cache entry to `state.effectiveBalanceIncrements` of the head state. The [checkpoint balance cache](https://raw.githubusercontent.com/ChainSafe/lodestar/e4b4f615/packages/beacon-node/src/chain/balancesCache.ts#L15-L44) uses the zero-inactive getter; the [closed issue #10173](https://api.github.com/repos/ChainSafe/lodestar/issues/10173) confirms it zeroes active slashed entries. | Slashed/inactive status **alone does not prove a difference**: if head and justified effective balances match, fallback reconstructs the spec's value. If the effective balance changes after the justified state, the error is `head EB − justified EB`; [epoch processing can update effective balance](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/beacon-chain.md#L2275-L2291). A 32→31 ETH change with 160 ETH ordinary weight and 192 ETH threshold makes spec **not weak** and Lodestar **weak**. The [post-Gloas caller](https://raw.githubusercontent.com/ChainSafe/lodestar/e4b4f615/packages/fork-choice/src/forkChoice/forkChoice.ts#L1820-L1865) is reached during [score updates](https://raw.githubusercontent.com/ChainSafe/lodestar/e4b4f615/packages/fork-choice/src/forkChoice/forkChoice.ts#L657-L683). | **Medium confidence, conditional; fork-choice divergence.** The arithmetic flips, but this review did not construct a complete state-transition trace for that exact 32→31 example. |
| **Nimbus** | [`fork_choice_epbs.nim#L130-L158`](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/fork_choice_epbs.nim#L130-L158) uses proto-array base weight and adds justified `effective_balance` for equivocators; [`proto_array.nim#L267-L304`](https://raw.githubusercontent.com/status-im/nimbus-eth2/0abc0dd0/beacon_chain/fork_choice/proto_array.nim#L267-L304) can retain boost in node weight. | FULL-variant omission is bounded to zero for the previous-slot-parent check by the delayed-attestation rule. Child-boost contamination is arithmetically capable of a threshold flip but no valid supplied-timing trace was established. | **Unproved for this candidate; no confirmed severity claim.** [Teku's note](https://raw.githubusercontent.com/Consensys/teku/2c35574b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/util/ForkChoiceUtilGloas.java#L533-L539) independently flags proto-array base weight as potentially boost-bearing, but is not evidence of a reachable Nimbus flip. |

## Known check and public results

The public [Lodestar `is_head_weak` search](https://api.github.com/search/issues?q=repo%3AChainSafe%2Flodestar%20is_head_weak) finds [#9232 open](https://api.github.com/repos/ChainSafe/lodestar/issues/9232) (general Gloas equivocator add-back), [#9654 merged](https://api.github.com/repos/ChainSafe/lodestar/pulls/9654) (implementation), and [#9339 closed unmerged](https://api.github.com/repos/ChainSafe/lodestar/pulls/9339). These do **not** document the precise head-state fallback discrepancy as fixed. [#10173 closed](https://api.github.com/repos/ChainSafe/lodestar/issues/10173) concerns the *threshold denominator*, which the [pinned spec now excludes slashed validators from](https://raw.githubusercontent.com/ethereum/consensus-specs/8a80ffa8/specs/phase0/fork-choice.md#L297-L307). The [Grandine exact-term issue/PR search](https://api.github.com/search/issues?q=repo%3Agrandinetech%2Fgrandine%20is_head_weak) returned no matches; [Nimbus exact-term search](https://api.github.com/search/issues?q=repo%3Astatus-im%2Fnimbus-eth2%20is_head_weak) returned only [#8559 merged](https://api.github.com/repos/status-im/nimbus-eth2/pulls/8559), a general ePBS fork-choice implementation PR. For context, [Lighthouse #9593](https://api.github.com/repos/sigp/lighthouse/issues/9593) and [#9831](https://api.github.com/repos/sigp/lighthouse/issues/9831) are open, but are not a known fix for these clients.

The public [Glamsterdam Hive group](https://hive.ethpandaops.io/#/group/glamsterdam) exposes no case-specific result in the page available to this review; a [devnet-11 plan](https://notes.ethereum.org/@ethpandaops/glamsterdam-devnet-11) describes happy-path testing and links that group, not a slashed-equivocator threshold test. [Execution-spec-tests Glamsterdam releases](https://github.com/ethereum/execution-specs/releases/tag/tests-glamsterdam-devnet@v8.1.4) concern execution-layer fixtures, so they do not independently establish this consensus fork-choice behavior. No public result demonstrating this exact condition was found. **No blockchain network or node was queried; only public source pages and APIs were read.**
