Skip to content

Only clear builder payment if the slashed validator is the proposer - #5365

Merged
jtraglia merged 4 commits into
ethereum:masterfrom
luca-zanolini:patch-1
Jun 17, 2026
Merged

jtraglia merged 4 commits into
ethereum:masterfrom
luca-zanolini:patch-1

Conversation

@luca-zanolini

Copy link
Copy Markdown
Contributor

Summary

In Gloas, process_proposer_slashing clears the BuilderPendingPayment IOU for a slot using only the slot number from the slashing evidence (header_1.slot), with no check that the slashed validator was that slot's proposer.

slot = header_1.slot
proposal_epoch = compute_epoch_at_slot(slot)
if proposal_epoch == get_current_epoch(state):
payment_index = SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH
state.builder_pending_payments[payment_index] = BuilderPendingPayment()
elif proposal_epoch == get_previous_epoch(state):
payment_index = slot % SLOTS_PER_EPOCH
state.builder_pending_payments[payment_index] = BuilderPendingPayment()

The grief vector

process_proposer_slashing validates a slashing by checking only that the two headers are consistent with each other — never that the proposer was the one actually assigned to the slot. Because the IOU is then cleared using only header_1.slot, the slashed validator does not have to be the slot's proposer for the clear to fire.

Concretely:

  1. Honest proposer P proposes the canonical block at slot N with a positive bid, arming a BuilderPendingPayment at slot N's index.
  2. A different slashable validator V signs two distinct block headers that both claim slot = N. These are valid slashing evidence (V is equivocating), even though V was never slot N's proposer and the headers point to no real block.
  3. That slashing is included while slot N is still in the 2-epoch payment window. The clear keys on header_1.slot = N, so it zeroes P's IOU.

Result: P's payment is cancelled — the proposer is never credited, the builder is never charged — purely because V equivocated on the same slot number.

Impact

A validator can be sacrificed (one slashing) to cancel an honest proposer's builder payment. It applies to payments not yet settled via an on-chain payload reveal.

Fix

Bind each IOU to the proposer that armed it, and clear it only on a match:

  • Add proposer_index: ValidatorIndex to BuilderPendingPayment.
  • Set it from block.proposer_index in process_execution_payload_bid.
  • In process_proposer_slashing, clear the entry only when payment.proposer_index == header_1.proposer_index.

The arming proposer can't be recomputed at slashing time for the previous-epoch case (proposer_lookahead no longer covers it), so the binding is stored on the entry. block.proposer_index is sound to record because process_block_header (run before the bid) asserts it equals get_beacon_proposer_index(state).

@github-actions github-actions Bot added the gloas label Jun 16, 2026

@potuz potuz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Very nice catch! This looks good to me, we need a regression test only. Perhaps we can have a solution without modifying the container, but looking for the proposer index in the previous epoch is awkward. I don' t see much problem in having the proposer index in the pending payments though.

Extend check_proposer_slashing_effect to expect the builder payment cleared
only when the slashed validator is the payment's proposer (mirroring the spec
guard), record the test payment for the slashed proposer by default, and add a
regression test asserting that slashing a different validator equivocating on
the same slot leaves an honest proposer's payment intact.
@github-actions github-actions Bot added the testing CI, actions, tests, testing infra label Jun 16, 2026
@luca-zanolini

Copy link
Copy Markdown
Contributor Author

Thanks! Added the regression test (test_builder_payment_not_deleted_foreign_equivocation): it records a slot's payment for one proposer, slashes a different validator equivocating on the same slot, and asserts the payment is left intact. I also extended the shared check_proposer_slashing_effect to expect the entry cleared only when the slashed validator is the payment's proposer (mirroring the new guard), and updated the slashing-test helper to record the payment for the slashed proposer by default.

On avoiding the container change: I went the same way you did -- the proposer can't be recomputed at slashing time for the previous-epoch case (proposer_lookahead only covers current and future epochs), so storing proposer_index on the pending payment seemed the cleanest option.

Ran the full gloas fork suite + the eip8025 shard + make lint locally, all green.

Comment thread specs/gloas/beacon-chain.md Outdated
@jtraglia jtraglia changed the title Gloas: bind builder payment IOU clearing to the slashed proposer Only clear builder payment if the slashed validator is the proposer Jun 16, 2026
Comment thread specs/gloas/beacon-chain.md
Use 'associated with' instead of 'armed' (per review).

@jtraglia jtraglia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thank you @luca-zanolini!

Comment thread specs/gloas/beacon-chain.md
@jtraglia
jtraglia merged commit b7b3578 into ethereum:master Jun 17, 2026
16 checks passed
jtraglia added a commit that referenced this pull request Jun 17, 2026
Related to:

* #5364
* #5365

I merged these back-to-back without pulling in updates from master &
didn't realize that they conflicted. The first removes block as a
parameter which the second uses to get the proposer index. Thankfully,
the fix is simple; we can use `get_beacon_proposer_index` for this. It's
worth mentioning that `process_block_header` checks that these are the
same, so this change is safe.
ensi321 added a commit to ChainSafe/lodestar that referenced this pull request Jun 26, 2026
implements the following spec changes required to pass `v1.7.0-alpha.11`
spec tests
- ethereum/consensus-specs#5359
- ethereum/consensus-specs#5377
- ethereum/consensus-specs#5373
- ethereum/consensus-specs#5365
- ethereum/consensus-specs#5364
- ethereum/consensus-specs#5368

---------

Co-authored-by: NC <17676176+ensi321@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gloas testing CI, actions, tests, testing infra

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants