@@ -423,3 +423,55 @@ def test_builder_payment_deletion_previous_epoch_last_slot(spec, state):
423423 pre_state ,
424424 proposer_slashing ,
425425 )
426+
427+
428+ @with_gloas_and_later
429+ @spec_state_test
430+ def test_builder_payment_not_deleted_foreign_equivocation (spec , state ):
431+ """
432+ Test that a proposer slashing does NOT delete a builder pending payment recorded for
433+ a different proposer. This guards against a griefing vector: the clear is keyed only
434+ by header slot, so without the proposer check any validator equivocating on a slot
435+ could clear an honest proposer's payment for that slot.
436+
437+ Input State Configured:
438+ - builder_pending_payments: current-epoch entry recorded for the payment proposer
439+ - proposer_slashing: valid slashing of a different validator for the same slot,
440+ within the 2-epoch window
441+
442+ Output State Verified:
443+ - validators[slashed_index].slashed: True
444+ - builder_pending_payments: entry left intact (slashed validator is not the
445+ payment's proposer)
446+ """
447+ active = spec .get_active_validator_indices (state , spec .get_current_epoch (state ))
448+ payment_proposer = active [0 ] # the payment is recorded for this proposer
449+ slashed_proposer = active [- 1 ] # equivocates on the slot, not the payment's proposer
450+ assert payment_proposer != slashed_proposer
451+
452+ proposer_slashing , _ = prepare_process_proposer_slashing (
453+ spec ,
454+ state ,
455+ advance_epochs = 2 ,
456+ slot_offset = Random (2024 ).randrange (spec .SLOTS_PER_EPOCH ),
457+ proposer_index = slashed_proposer ,
458+ parent_root_2 = b"\x99 " * 32 , # Make headers different
459+ builder_payment_amount = spec .MIN_ACTIVATION_BALANCE ,
460+ builder_payment_fee_recipient = b"\x42 " * 20 ,
461+ builder_payment_weight = 1000 ,
462+ builder_payment_proposer_index = payment_proposer ,
463+ )
464+
465+ slashed_slot = proposer_slashing .signed_header_1 .message .slot
466+ assert spec .compute_epoch_at_slot (slashed_slot ) == spec .get_current_epoch (state )
467+
468+ pre_state = state .copy ()
469+
470+ yield from run_proposer_slashing_processing (spec , state , proposer_slashing )
471+
472+ assert_process_proposer_slashing (
473+ spec ,
474+ state ,
475+ pre_state ,
476+ proposer_slashing ,
477+ )
0 commit comments