# `process_proposer_slashing` Test Specification *Note*: This report is AI-generated and is not an authoritative source of truth. ## Function Signature ```python def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None ``` Location: [specs/gloas/beacon-chain.md](../../../../../../../specs/gloas/beacon-chain.md) ______________________________________________________________________ ## Input Space (State and Operation Fields Read) ### `ProposerSlashing` Container | Field | Type | Value Space | Purpose | | ----------------- | ------------------------- | ---------------------------------- | -------------------------------------- | | `signed_header_1` | `SignedBeaconBlockHeader` | Container with message + signature | First conflicting signed block header | | `signed_header_2` | `SignedBeaconBlockHeader` | Container with message + signature | Second conflicting signed block header | ### `SignedBeaconBlockHeader` Fields | Field | Type | Value Space | Purpose | | ------------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `message.slot` | `Slot` | `uint64` in [0, 2^64-1]. **Bound to** `header_2.slot`: must match. | Slot of the block header | | `message.proposer_index` | `ValidatorIndex` | `uint64` in \[0, `len(validators)`-1\]. **Bound to** `header_2.proposer_index`: must match. **Index into** `validators[]`. | Index of the proposer | | `message.parent_root` | `Root` | `Bytes32`. Must differ from `header_2` in at least one root field. | Parent block root | | `message.state_root` | `Root` | `Bytes32`. Must differ from `header_2` in at least one root field. | State root | | `message.body_root` | `Root` | `Bytes32`. Must differ from `header_2` in at least one root field. | Block body root | | `signature` | `BLSSignature` | `Bytes96`. **Bound to** `message` and `proposer.pubkey`: must be valid BLS signature. | BLS signature over the message | ### Relevant `BeaconState` Fields | Field | Type | Value Space | Purpose | | -------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------- | | `state.slot` | `Slot` | `uint64` in [0, 2^64-1]. Derives `epoch = slot // SLOTS_PER_EPOCH`. | Compute current epoch for slashability check | | `state.validators` | `List[Validator, VALIDATOR_REGISTRY_LIMIT]` | Length in [0, 2^40]. All proposer index references must be < len. | Validator registry | | `state.balances` | `List[Gwei, VALIDATOR_REGISTRY_LIMIT]` | `uint64[]`. **Same length as** `validators[]`. | Validator balances (modified by slashing) | | `state.slashings` | `Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR]` | `uint64[8192]`. Indexed by `epoch % EPOCHS_PER_SLASHINGS_VECTOR`. | Slashings accumulator | | `state.builder_pending_payments` | `Vector[BuilderPendingPayment, 2 * SLOTS_PER_EPOCH]` | `Container[64]`. [GLOAS] Indexed by slot position within 2-epoch window. | Pending builder payments for current and previous epoch | ### `Validator` Fields (Relevant) | Field | Type | Value Space | Purpose | | -------------------- | ----------- | --------------------------------------------------------------------------------- | ----------------------------------------------- | | `pubkey` | `BLSPubkey` | `Bytes48`. Used to verify both header signatures. | Validator public key for signature verification | | `slashed` | `bool` | `True` or `False`. **Constraint**: must be `False` for slashability. | Whether validator has been slashed | | `activation_epoch` | `Epoch` | `uint64`. **Bound to** `state.slot`: must be `<= current_epoch` for slashability. | Epoch when validator became active | | `exit_epoch` | `Epoch` | `uint64`. Set during slashing if `FAR_FUTURE_EPOCH`. | Epoch when validator will exit | | `withdrawable_epoch` | `Epoch` | `uint64`. **Bound to** `state.slot`: must be `> current_epoch` for slashability. | Epoch when validator can withdraw | ______________________________________________________________________ ## Output Space (State Fields Modified) | Field | Type | Value Space | Modification | | ------------------------------------------------------ | ----------------------- | --------------------------------------------------------------------- | ------------------------------------------------------ | | `state.validators[proposer_index].slashed` | `bool` | `True` | **Set** to `True` | | `state.validators[proposer_index].exit_epoch` | `Epoch` | `max(exit_epoch, current_epoch + 1)` | **Set** to exit epoch per slashing rules | | `state.validators[proposer_index].withdrawable_epoch` | `Epoch` | `exit_epoch + EPOCHS_PER_SLASHINGS_VECTOR` | **Set** to withdrawable epoch per slashing rules | | `state.balances[proposer_index]` | `Gwei` | `uint64`. New value = old - penalty. | **Decreased** by slash penalty | | `state.balances[whistleblower_index]` | `Gwei` | `uint64`. New value = old + reward. | **Increased** by whistleblower reward | | `state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR]` | `Gwei` | `uint64`. New value = old + effective_balance. | **Increased** by slashed validator's effective balance | | `state.builder_pending_payments[payment_index]` | `BuilderPendingPayment` | Empty container. [GLOAS] Only if proposal slot within 2-epoch window. | **Cleared** to empty `BuilderPendingPayment()` | ______________________________________________________________________ ## Validation Conditions The function asserts the following conditions (failure causes revert): 1. **Slots match**: `header_1.slot == header_2.slot` 2. **Proposer indices match**: `header_1.proposer_index == header_2.proposer_index` 3. **Headers are different**: `header_1 != header_2` 4. **Proposer is slashable**: `is_slashable_validator(proposer, get_current_epoch(state))` - Validator is not already slashed - Validator is active (activation_epoch \<= current_epoch) - Validator has not yet reached withdrawable epoch (current_epoch \< withdrawable_epoch) 5. **Signature 1 is valid**: `bls.Verify(proposer.pubkey, signing_root_1, signature_1)` 6. **Signature 2 is valid**: `bls.Verify(proposer.pubkey, signing_root_2, signature_2)` ______________________________________________________________________ ## Key Constants | Constant | Value (Mainnet) | Description | | --------------------------------------- | -------------------------- | ---------------------------------------------------------- | | `MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA` | `4096` | Divisor for initial slash penalty (inherited from Electra) | | `WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA` | `4096` | Divisor for whistleblower reward (inherited from Electra) | | `SLOTS_PER_EPOCH` | `32` | Slots per epoch (used for payment index calculation) | | `EPOCHS_PER_SLASHINGS_VECTOR` | `8192` | Length of slashings accumulator vector | | `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` | Domain for block header signing | | `FAR_FUTURE_EPOCH` | `2^64-1` | Sentinel for "not set" epoch | ______________________________________________________________________ ## Key Cross-Field Constraints 1. **Index Alignment**: - `len(validators) == len(balances)` always - `proposer_index` must be `< len(validators)` 2. **Header Consistency**: Both headers must have identical `slot` and `proposer_index` fields 3. **Header Distinction**: The headers must differ in at least one of: `parent_root`, `state_root`, or `body_root` 4. **Proposer Validity**: The `proposer_index` must reference a valid, active, non-slashed validator 5. **Signature Binding**: Both signatures must be valid BLS signatures from the proposer's public key over the respective header messages with the correct domain 6. **[GLOAS] Payment Window**: Builder pending payment deletion only occurs if proposal slot is within current or previous epoch ______________________________________________________________________ ## GLOAS-Specific: Builder Pending Payment Deletion In the GLOAS fork, when a proposer is slashed, any associated builder pending payment is deleted if the proposal is within a 2-epoch window: ```python proposal_epoch = compute_epoch_at_slot(slot) if proposal_epoch == get_current_epoch(state): # Current epoch: index in second half of array payment_index = SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH state.builder_pending_payments[payment_index] = BuilderPendingPayment() elif proposal_epoch == get_previous_epoch(state): # Previous epoch: index in first half of array payment_index = slot % SLOTS_PER_EPOCH state.builder_pending_payments[payment_index] = BuilderPendingPayment() # If proposal_epoch < previous_epoch: no payment deletion (too late) ``` ### Payment Index Mapping | Proposal Epoch | Payment Index Formula | Array Range | | -------------- | ------------------------------------------ | ----------- | | Current epoch | `SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH` | `[32, 63]` | | Previous epoch | `slot % SLOTS_PER_EPOCH` | `[0, 31]` | | Older | No deletion | N/A | ______________________________________________________________________ ## Functions Called Call stack for `process_proposer_slashing`. Sources: gloas functions are defined in [gloas/beacon-chain.md](../../../../../../../specs/gloas/beacon-chain.md); inherited functions are from earlier forks (phase0, altair, electra). - `process_proposer_slashing(state, proposer_slashing)` — gloas - `is_slashable_validator(validator, epoch)` — phase0 - `get_domain(state, domain_type, epoch)` — phase0 - `compute_epoch_at_slot(slot)` — phase0 - `compute_signing_root(message, domain)` — phase0 - `bls.Verify(pubkey, message, signature)` — BLS library - `get_current_epoch(state)` — phase0 - `get_previous_epoch(state)` — phase0 - `compute_epoch_at_slot(slot)` — phase0 - `slash_validator(state, slashed_index)` — electra - `initiate_validator_exit(state, index)` — phase0 - `decrease_balance(state, index, amount)` — phase0 - `increase_balance(state, index, amount)` — phase0 ______________________________________________________________________ ## Test Coverage Summary ### Inherited from Phase0 (15 tests) | Test | Validity | Coverage | | -------------------------------------------------- | -------- | --------------------------------------------- | | `test_basic` | Valid | Basic proposer slashing with valid signatures | | `test_slashed_and_proposer_index_the_same` | Valid | Block proposer slashes themselves | | `test_block_header_from_future` | Valid | Headers from future slots are allowed | | `test_invalid_incorrect_sig_1` | Invalid | First signature is invalid | | `test_invalid_incorrect_sig_2` | Invalid | Second signature is invalid | | `test_invalid_incorrect_sig_1_and_2` | Invalid | Both signatures are invalid | | `test_invalid_incorrect_sig_1_and_2_swap` | Invalid | Signatures swapped between headers | | `test_invalid_incorrect_proposer_index` | Invalid | Proposer index out of bounds | | `test_invalid_different_proposer_indices` | Invalid | Headers have different proposer indices | | `test_invalid_slots_of_different_epochs` | Invalid | Header slots are in different epochs | | `test_invalid_headers_are_same_sigs_are_same` | Invalid | Headers are identical (same signatures) | | `test_invalid_headers_are_same_sigs_are_different` | Invalid | Headers are identical (different signatures) | | `test_invalid_proposer_is_not_activated` | Invalid | Proposer has not yet activated | | `test_invalid_proposer_is_slashed` | Invalid | Proposer is already slashed | | `test_invalid_proposer_is_withdrawn` | Invalid | Proposer is already withdrawn | ### GLOAS-Specific (3 tests) | Test | Coverage | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `test_builder_payment_deletion_current_epoch` | Builder pending payment is deleted when proposer is slashed in the same epoch as the proposal | | `test_builder_payment_deletion_previous_epoch` | Builder pending payment is deleted when proposer is slashed in the epoch after the proposal | | `test_builder_payment_deletion_too_late` | Builder pending payment is NOT deleted when slashing comes more than 2 epochs after the proposal | ______________________________________________________________________ ## Summary Diagram ```text +-----------------------------------------------------------+ | INPUT (Read) | +-----------------------------------------------------------+ | ProposerSlashing: | | signed_header_1.message.slot : uint64 | | signed_header_1.message.proposer_index : uint64 | | signed_header_1.message.parent_root : Bytes32 | | signed_header_1.message.state_root : Bytes32 | | signed_header_1.message.body_root : Bytes32 | | signed_header_1.signature : Bytes96 | | signed_header_2 (same fields) | | BeaconState: | | slot : uint64 | | validators[proposer_index] : Container | | .pubkey, .slashed, .activation_epoch, | | .exit_epoch, .withdrawable_epoch | | builder_pending_payments[0..63] : Container[] | +---------------------------+-------------------------------+ | v +-----------------------------------------------------------+ | process_proposer_slashing() | | | | 1. Validate header slots match | | 2. Validate proposer indices match | | 3. Validate headers are different | | 4. Validate proposer is slashable | | 5. Verify signature 1 | | 6. Verify signature 2 | | 7. [GLOAS] Delete builder pending payment if in window | | 8. Execute slash_validator() | +---------------------------+-------------------------------+ | v +-----------------------------------------------------------+ | OUTPUT (Modified) | +-----------------------------------------------------------+ | validators[proposer_index].slashed : True | | validators[proposer_index].exit_epoch : Epoch | | validators[proposer_index].withdrawable_epoch : Epoch | | balances[proposer_index] : decreased | | balances[whistleblower_index] : increased | | slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] : increased| | builder_pending_payments[payment_index] : cleared | +-----------------------------------------------------------+ ```