Defragment: Implement EIP-7251 (MaxEB)
======
TL;DR:
------
1. [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) improves MaxEB, and is [scoping](https://eips.ethereum.org/EIPS/eip-7600) into [Electra](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/) Upgrade ([~2025/Q1](https://x.com/TimBeiko/status/1793684244612407687)), and with the mission to [sustain validator set size growth](https://ethresear.ch/t/sticking-to-8192-signatures-per-slot-post-ssf-how-and-why/17989), and preparing for [SSF](https://ethereum.org/en/roadmap/single-slot-finality/) and [ePBS](https://ethereum.org/en/roadmap/pbs/).
2. I have [studied](https://hackmd.io/@georgesheth/HJKkx3NSR) that [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) has 6 features and explains what they are.
3. This article aims to explain how to implement all these features for [Electra](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/) Upgrade at coding level ([Spec](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/) and [Test](https://github.com/ethereum/execution-spec-tests)). In summary,
1. Feature `1, 2, 3, 4` are fully implemented.
2. Feature `5` needs to be completed by implementing one missing part and Feature `6` needs to be fully implemented.
4. Welcome suggestion and collaboration.
5. My other relevent articles on EIP-7251:
- [BeaconState and Validator Balance for EIP-7251](https://hackmd.io/@georgesheth/BJGl24HYA)
- [History of EIP-7251](https://hackmd.io/@georgesheth/rJxnQBrtC)
- [Feature Lists of EIP-7251](https://hackmd.io/@georgesheth/HJKkx3NSR)
- [Implement EIP-7251](https://hackmd.io/@georgesheth/Hk2r2BHFC)
- [Develop Push Based Custom Ceiling Partial Feature Withdraw for EIP-7251](https://hackmd.io/@georgesheth/BJ5tkLBtA)
Feature 1: Update `validator.effective_balance`
===============================================
Feature Description:
--------------------
1. Elastic`validator.effective_balance` to large value range.
2. by increasing the`MAX_EFFECTIVE_BALANCE`.
3. while creating a `MIN_ACTIVATION_BALANCE`.
After EIP-7251 Looklike:
------------------------
1. `MIN_ACTIVATION_BALANCE`sets the threadhold to activate a validator, which is `32 ETH` fixed.
2. `MAX_EFFECTIVE_BALANCE` also sets the threadhold to cap for `validator.active_balance` which is `2048 ETH` fixed.
3. `validator.active_balance` is an integer value from `[0, 1, ..., 31, 32, ..., 2047, 2048] ETH` which is controlled by `EFFECTIVE_BALANCE_INCREMENT` (`1 ETH` fixed)
How to implement:
-----------------
1. Update the value of `MAX_EFFECTIVE_BALANCE_ELECTRA`
2. Create `MIN_ACTIVATION_BALANCE` as a new constant.
3. Exam the impact of `validator.effective_balance`
### Spec change on Constants
| Name | Value | Comments |
| ---- | ---- | ---- |
| MIN_ACTIVATION_BALANCE | Gwei(`2**5 * 10**9`) (= 32,000,000,000) | equavilent to MAX_EFFECTIVE_BALANCE = 32 ETH since Phase0 |
| MAX_EFFECTIVE_BALANCE_ELECTRA | Gwei(`2**11 * 10**9`) (= 2048,000,000,000) | updated from 32 ETH to 2048 ETH |
### Impact of updating the value of `MAX_EFFECTIVE_BALANCE_ELECTRA`
1. No logical changes in `compute_proposer_index()`. Only modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA`**.**
2. No logical changes in `get_next_sync_committee_indices()`. Only modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA`.
3. New `get_validator_max_effective_balancefor()` to help backward compatibility.
```python=
def get_validator_max_effective_balance(validator: Validator) -> Gwei:
"""
Get max effective balance for ``validator``.
"""
if has_compounding_withdrawal_credential(validator):
return MAX_EFFECTIVE_BALANCE_ELECTRA
else:
return MIN_ACTIVATION_BALANCE
```
### Impact of creating `MIN_ACTIVATION_BALANCE`
1. Modified `is_eligible_for_activation_queue` to use `MIN_ACTIVATION_BALANCE` instead of `MAX_EFFECTIVE_BALANCE`.
```python=
def is_eligible_for_activation_queue(validator: Validator) -> bool:
"""
Check if ``validator`` is eligible to be placed into the activation queue.
"""
return (
validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH
and validator.effective_balance >= MIN_ACTIVATION_BALANCE # [Modified in Electra:EIP7251]
)
```
### Impact on how to process `validator.effective_balance`
1. Modified `process_effective_balance_updates()` to use the new limit for the maximum effective balance.
```python=
def process_effective_balance_updates(state: BeaconState) -> None:
# Update effective balances with hysteresis
for index, validator in enumerate(state.validators):
balance = state.balances[index]
HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT)
DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER
UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER
# [Modified in Electra:EIP7251]
EFFECTIVE_BALANCE_LIMIT = (
MAX_EFFECTIVE_BALANCE_ELECTRA if has_compounding_withdrawal_credential(validator)
else MIN_ACTIVATION_BALANCE
)
if (
balance + DOWNWARD_THRESHOLD < validator.effective_balance
or validator.effective_balance + UPWARD_THRESHOLD < balance
):
validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, EFFECTIVE_BALANCE_LIMIT)
```
Feature 2: Enable validator consolidation (optional):
=====================================================
After EIP-7251 Looklike :
-------------------------
1. Stakers may like to consolidate their validators.
2. It is optional, and gasless.
3. Rely on [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) to generate a validator consolidation request.
How to implement:
-----------------
1. Create new container for `ConsolidationRequest` and `PendingConsolidation`
2. Queue Control of `ConsolidationRequest`
3. Help function of `get_active_balance`
4. Process `ConsolidationRequest`
### Impact of new `ConsolidationRequest` and `PendingConsolidation`
1. `ConsolidationRequest`[¶](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#consolidationrequest)*Note*: The container is new in EIP7251.
```python=
class ConsolidationRequest(Container):
source_address: ExecutionAddress
source_pubkey: BLSPubkey
target_pubkey: BLSPubkey
```
1. `PendingConsolidation`[¶](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#pendingconsolidation)*Note*: The container is new in EIP7251.
```python=
class PendingConsolidation(Container):
source_index: ValidatorIndex
target_index: ValidatorIndex
```
### Impact of process of ConsolidationRequest queue
| Name | Value | Description |
| ---- | ---- | ---- |
| MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD | uint64(1) (= 1) | [New in Electra:EIP7002] Maximum number of execution layer consolidation requests in each payload |
1. New `get_consolidation_churn_limit()`
```python=
def get_consolidation_churn_limit(state: BeaconState) -> Gwei:
return get_balance_churn_limit(state) - get_activation_exit_churn_limit(state)
```
1. New `compute_consolidation_epoch_and_update_churn`
```python=
def compute_consolidation_epoch_and_update_churn(state: BeaconState, consolidation_balance: Gwei) -> Epoch:
earliest_consolidation_epoch = max(
state.earliest_consolidation_epoch, compute_activation_exit_epoch(get_current_epoch(state)))
per_epoch_consolidation_churn = get_consolidation_churn_limit(state)
# New epoch for consolidations.
if state.earliest_consolidation_epoch < earliest_consolidation_epoch:
consolidation_balance_to_consume = per_epoch_consolidation_churn
else:
consolidation_balance_to_consume = state.consolidation_balance_to_consume
# Consolidation doesn't fit in the current earliest epoch.
if consolidation_balance > consolidation_balance_to_consume:
balance_to_process = consolidation_balance - consolidation_balance_to_consume
additional_epochs = (balance_to_process - 1) // per_epoch_consolidation_churn + 1
earliest_consolidation_epoch += additional_epochs
consolidation_balance_to_consume += additional_epochs * per_epoch_consolidation_churn
# Consume the balance and update state variables.
state.consolidation_balance_to_consume = consolidation_balance_to_consume - consolidation_balance
state.earliest_consolidation_epoch = earliest_consolidation_epoch
return state.earliest_consolidation_epoch
```
### Impact on Validator.effective_balance
1. New `get_active_balance`
```python=
def get_active_balance(state: BeaconState, validator_index: ValidatorIndex) -> Gwei:
max_effective_balance = get_validator_max_effective_balance(state.validators[validator_index])
return min(state.balances[validator_index], max_effective_balance)
```
### Impact of processing `ConsolidationRequest`
1. New `process_pending_consolidations`
```python=
def process_pending_consolidations(state: BeaconState) -> None:
next_pending_consolidation = 0
for pending_consolidation in state.pending_consolidations:
source_validator = state.validators[pending_consolidation.source_index]
if source_validator.slashed:
next_pending_consolidation += 1
continue
if source_validator.withdrawable_epoch > get_current_epoch(state):
break
# Churn any target excess active balance of target and raise its max
switch_to_compounding_validator(state, pending_consolidation.target_index)
# Move active balance to target. Excess balance is withdrawable.
active_balance = get_active_balance(state, pending_consolidation.source_index)
decrease_balance(state, pending_consolidation.source_index, active_balance)
increase_balance(state, pending_consolidation.target_index, active_balance)
next_pending_consolidation += 1
state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:]
```
1. New `process_consolidation_request` for Execution layer consolidation requests
```python=
def process_consolidation_request(
state: BeaconState,
consolidation_request: ConsolidationRequest
) -> None:
# If the pending consolidations queue is full, consolidation requests are ignored
if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT:
return
# If there is too little available consolidation churn limit, consolidation requests are ignored
if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE:
return
validator_pubkeys = [v.pubkey for v in state.validators]
# Verify pubkeys exists
request_source_pubkey = consolidation_request.source_pubkey
request_target_pubkey = consolidation_request.target_pubkey
if request_source_pubkey not in validator_pubkeys:
return
if request_target_pubkey not in validator_pubkeys:
return
source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey))
source_validator = state.validators[source_index]
target_validator = state.validators[target_index]
# Verify that source != target, so a consolidation cannot be used as an exit.
if source_index == target_index:
return
# Verify source withdrawal credentials
has_correct_credential = has_execution_withdrawal_credential(source_validator)
is_correct_source_address = (
source_validator.withdrawal_credentials[12:] == consolidation_request.source_address
)
if not (has_correct_credential and is_correct_source_address):
return
# Verify that target has execution withdrawal credentials
if not has_execution_withdrawal_credential(target_validator):
return
# Verify the source and the target are active
current_epoch = get_current_epoch(state)
if not is_active_validator(source_validator, current_epoch):
return
if not is_active_validator(target_validator, current_epoch):
return
# Verify exits for source and target have not been initiated
if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
return
if target_validator.exit_epoch != FAR_FUTURE_EPOCH:
return
# Initiate source validator exit and append pending consolidation
source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(
state, source_validator.effective_balance
)
source_validator.withdrawable_epoch = Epoch(
source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
)
state.pending_consolidations.append(PendingConsolidation(
source_index=source_index,
target_index=target_index
))
```
Feature 3: Enable compounding rewards management.
=================================================
After EIP-7251 Looklike:
------------------------
1. Earn compounding rewards when `validator.effective_balance` > `32 ETH`
2. Earn compounding rewards only stop when `validator.effective_balance` > `2048 ETH`.
How to implement:
-----------------
1. Create new container for `PendingBalanceDeposit`
2. Enable compounding validator switch for backward compatibility
3. All new help functions to identify compounding validators.
### Impact of new `PendingBalanceDeposit`
1. New `PendingBalanceDeposit` in EIP7251
```python=
class PendingBalanceDeposit(Container):
index: ValidatorIndex
amount: Gwei
```
### Impact of compounding validator switch function
1. New `queue_excess_active_balance`
```python=
def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> None:
balance = state.balances[index]
if balance > MIN_ACTIVATION_BALANCE:
excess_balance = balance - MIN_ACTIVATION_BALANCE
state.balances[index] = MIN_ACTIVATION_BALANCE
state.pending_balance_deposits.append(
PendingBalanceDeposit(index=index, amount=excess_balance) # [Modified in Electra:EIP7251]
)
```
1. New `switch_to_compounding_validator`
```python=
def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None:
validator = state.validators[index]
if has_eth1_withdrawal_credential(validator):
validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
queue_excess_active_balance(state, index)
```
### Impact of adding new help function to identify compounding validators.
1. New `is_compounding_withdrawal_credential`
```python=
def is_compounding_withdrawal_credential(withdrawal_credentials: Bytes32) -> bool:
return withdrawal_credentials[:1] == COMPOUNDING_WITHDRAWAL_PREFIX
```
1. New `has_compounding_withdrawal_credential`
```python=
def has_compounding_withdrawal_credential(validator: Validator) -> bool:
"""
Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential.
"""
return is_compounding_withdrawal_credential(validator.withdrawal_credentials)
```
1. New `has_execution_withdrawal_credential`
```python=
def has_execution_withdrawal_credential(validator: Validator) -> bool:
"""
Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential.
"""
return has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator)
```
Feature 4: Extend deposit and top-up.
=====================================
Looklike After EIP-7251:
------------------------
1. **Deposit for activation only**: Staker should deposit 32 ETH in one go for validator activation, and avoid [edge case](https://eth2book.info/capella/part2/incentives/balances/#an-edge-case).
2. **Deposit for activation and top-up for weight**: Staker would like to deposit more than 32 ETH in one go for validator activation and also increase weight and earn compounding rewards.
3. **Top-up for weight**: Staker would like to continue to top-up ETH to increase weight and earn compounding rewards
4. **Top-up to avoid ejection**: Staker may only deposit again if validator.effective_balance is under 32 ETH to top-up into 32 ETH again.
How to implement:
-----------------
1. Create a new `BalanceDeposit` container, in [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110)
2. Register validator and Queue `PendingBalanceDeposit`
3. Queue Control of `BalanceDeposit`
4. Process `PendingBalanceDeposit` queue
### Impact of adding new balance deposit
1. New `DepositRequest`. The container is new in EIP6110.
```python=
class DepositRequest(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32
amount: Gwei
signature: BLSSignature
index: uint64
```
1. New `PendingBalanceDeposit` in EIP7251
```python=
class PendingBalanceDeposit(Container):
index: ValidatorIndex
amount: Gwei
```
### Impact of add and queue `BalanceDeposit`
1. Updated `get_validator_from_deposit`
```python=
def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32) -> Validator:
return Validator(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
activation_eligibility_epoch=FAR_FUTURE_EPOCH,
activation_epoch=FAR_FUTURE_EPOCH,
exit_epoch=FAR_FUTURE_EPOCH,
withdrawable_epoch=FAR_FUTURE_EPOCH,
effective_balance=0, # [Modified in Electra:EIP7251]
)
```
1. Modified `add_validator_to_registry`
```python=
def add_validator_to_registry(state: BeaconState,
pubkey: BLSPubkey,
withdrawal_credentials: Bytes32,
amount: uint64) -> None:
index = get_index_for_new_validator(state)
validator = get_validator_from_deposit(pubkey, withdrawal_credentials)
set_or_append_list(state.validators, index, validator)
set_or_append_list(state.balances, index, 0) # [Modified in Electra:EIP7251]
set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000))
set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000))
set_or_append_list(state.inactivity_scores, index, uint64(0))
state.pending_balance_deposits.append(PendingBalanceDeposit(index=index, amount=amount)) # [New in Electra:EIP7251]
```
### Impact of Queue Control of `BalanceDeposit`
| Name | Value | Description |
| ---- | ---- | ---- |
| MAX_DEPOSIT_REQUESTS_PER_PAYLOAD | uint64(2**13) (= 8,192) | [New in Electra:EIP6110] Maximum number of deposit receipts allowed in each payload |
TODO: cross check how execution layer handles it.
### Impact of Process `PendingBalanceDeposit` queue
1. Updated `apply_deposit` for execution layer
```python=
def apply_deposit(state: BeaconState,
pubkey: BLSPubkey,
withdrawal_credentials: Bytes32,
amount: uint64,
signature: BLSSignature) -> None:
validator_pubkeys = [v.pubkey for v in state.validators]
if pubkey not in validator_pubkeys:
# Verify the deposit signature (proof of possession) which is not checked by the deposit contract
if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
add_validator_to_registry(state, pubkey, withdrawal_credentials, amount)
else:
# Increase balance by deposit amount
index = ValidatorIndex(validator_pubkeys.index(pubkey))
state.pending_balance_deposits.append(
PendingBalanceDeposit(index=index, amount=amount)
) # [Modified in Electra:EIP-7251]
# Check if valid deposit switch to compounding credentials
if (
is_compounding_withdrawal_credential(withdrawal_credentials)
and has_eth1_withdrawal_credential(state.validators[index])
and is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature)
):
switch_to_compounding_validator(state, index)
```
1. **New** **`process_deposit_request`****, This function is new in Electra:EIP6110**
```python=
def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
# Set deposit request start index
if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUESTS_START_INDEX:
state.deposit_requests_start_index = deposit_request.index
apply_deposit(
state=state,
pubkey=deposit_request.pubkey,
withdrawal_credentials=deposit_request.withdrawal_credentials,
amount=deposit_request.amount,
signature=deposit_request.signature,
)
```
Feature 5: Extend withdraw and exit:
====================================
Looklike After EIP-7251:
------------------------
1. **Voluntary Full Exit**: unstake everything and become excited.
2. **Rewards Sweep above MaxEB**: automatic partial withdrawal (gaslessly) excess balance when validator.active_balance is larger than 2048 ETH to maintain 2048 ETH consistently.
4. **Rewards Sweep above custom ceiling but less than MaxEB**: (TODO)
1. A validator can setup a custom ceiling for auto sweep. The custom ceiling should be a value between 32 to 2048 ETH.
2. Automatic partial withdrawal (gaslessly) excess balance when validator.active_balance is larger than custom ceiling.
How to implement:
-----------------
1. No change to Voluntary Full Exit.
2. No change to Rewards Sweep above MaxEB
3. For manual partial withdrawal, we need to add a PartialWithdrawal container and modify `is_partially_withdrawable_validator`, and then modify in `get_expected_withdrawals()`, and then start to process the manual partial withdrawal from the Execitional Layer.
4. Rewards Sweep above custom ceiling but less MaxEB:
1. The logic is still under development.
2. (TODO)
### Impacts of adding a new PartialWithdrawal container
1. `WithdrawalRequest`[¶](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#withdrawalrequest), *Note*: The container is new in EIP7251:EIP7002.
```python=
class WithdrawalRequest(Container):
source_address: ExecutionAddress
validator_pubkey: BLSPubkey
amount: Gwei
```
1. `PendingPartialWithdrawal`[¶](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#pendingpartialwithdrawal), *Note*: The container is new in EIP7251.
```python=
class PendingPartialWithdrawal(Container):
index: ValidatorIndex
amount: Gwei
withdrawable_epoch: Epoch
```
### Impact of Queue Control of `BalanceDeposit`
| Name | Value | Description |
| ---- | ---- | ---- |
| MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD | uint64(2**4) (= 16) | [New in Electra:EIP7002] Maximum number of execution layer withdrawal requests in each payload |
### Impacts of process manual partial withdraw request
1. Modified `is_partially_withdrawable_validator`to use `get_validator_max_effective_balance` instead of `MAX_EFFECTIVE_BALANCE` and `has_execution_withdrawal_credential` instead of `has_eth1_withdrawal_credential`.
```python=
def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> bool:
"""
Check if ``validator`` is partially withdrawable.
"""
max_effective_balance = get_validator_max_effective_balance(validator)
has_max_effective_balance = validator.effective_balance == max_effective_balance # [Modified in Electra:EIP7251]
has_excess_balance = balance > max_effective_balance # [Modified in Electra:EIP7251]
return (
has_execution_withdrawal_credential(validator) # [Modified in Electra:EIP7251]
and has_max_effective_balance
and has_excess_balance
)
```
1. Modified `get_expected_withdrawals()`
```python=
def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]:
... ...
withdrawals: List[Withdrawal] = []
# [New in Electra:EIP7251] Consume pending partial withdrawals
for withdrawal in state.pending_partial_withdrawals:
... ...
if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance:
withdrawable_balance = min(state.balances[withdrawal.index] - MIN_ACTIVATION_BALANCE, withdrawal.amount)
withdrawals.append(Withdrawal(
... ...
amount=withdrawable_balance,
))
withdrawal_index += WithdrawalIndex(1)
partial_withdrawals_count = len(withdrawals)
# Sweep for remaining.
bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
for _ in range(bound):
validator = state.validators[validator_index]
balance = state.balances[validator_index]
if is_fully_withdrawable_validator(validator, balance, epoch):
withdrawals.append(Withdrawal(
... ...
amount=balance,
))
withdrawal_index += WithdrawalIndex(1)
elif is_partially_withdrawable_validator(validator, balance):
withdrawals.append(Withdrawal(
... ...
amount=balance - get_validator_max_effective_balance(validator), # [Modified in Electra:EIP7251]
))
withdrawal_index += WithdrawalIndex(1)
if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
break
validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
return withdrawals, partial_withdrawals_count
```
1. Modified `process_withdrawal_request()`
```python=
def process_withdrawal_request(
state: BeaconState,
withdrawal_request: WithdrawalRequest
) -> None:
amount = withdrawal_request.amount
is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT # FULL_EXIT_REQUEST_AMOUNT = 0
if is_full_exit_request:
# Only exit validator if it has no pending withdrawals in the queue
if pending_balance_to_withdraw == 0:
initiate_validator_exit(state, index)
return
... ...
# Only allow partial withdrawals with compounding withdrawal credentials
if has_compounding_withdrawal_credential(validator) and has_sufficient_effective_balance and has_excess_balance:
to_withdraw = min(
state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw,
amount
)
... ...
state.pending_partial_withdrawals.append(PendingPartialWithdrawal(
index=index,
amount=to_withdraw,
withdrawable_epoch=withdrawable_epoch,
))
```
1. Modified `process_withdrawals()`
```python=
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
expected_withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) # [Modified in Electra:EIP7251]
assert len(payload.withdrawals) == len(expected_withdrawals)
for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
assert withdrawal == expected_withdrawal
decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
# Update pending partial withdrawals [New in Electra:EIP7251]
state.pending_partial_withdrawals = state.pending_partial_withdrawals[partial_withdrawals_count:]
# Update the next withdrawal index if this block contained withdrawals
if len(expected_withdrawals) != 0:
latest_withdrawal = expected_withdrawals[-1]
state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
# Update the next validator index to start the next withdrawal sweep
if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
# Next sweep starts after the latest withdrawal's validator index
next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators))
state.next_withdrawal_validator_index = next_validator_index
else:
# Advance sweep by the max length of the sweep if there was not a full set of withdrawals
next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
next_validator_index = ValidatorIndex(next_index % len(state.validators))
state.next_withdrawal_validator_index = next_validator_index
```
Feature 6: Update slashing and penalty:
=======================================
After EIP-7251 Looklike:
------------------------
1. **Initial slashing penalty**: fixed to a constant amount (1 ETH fixed, not 2048*1/32) or modified to scale sublinearly
2. For about 36 days, the validator is removed from the active validation set and is placed in the exit queue.
3. **Correlation penalty**: is modified to scale quadratically rather than linearly.
How to implement:
-----------------
1. still under discussion, but with below [proposal](https://ethresear.ch/t/slashing-penalty-analysis-eip-7251/16509) from Mike
2. Initial slashing penalty is fixed to a constant amount (1 ETH fixed, not 2048*1/32) or modified to scale sublinearly
3. Correlation penalty is modified to scale quadratically rather than linearly.
### Impacts of modifying Initial Slashing Penalty
1. Modified `slash_validator()`
```python=
def slash_validator(state: BeaconState,
slashed_index: ValidatorIndex,
whistleblower_index: ValidatorIndex=None) -> None:
... ...
state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
# existing logic
# decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT)
# TODO: to be add into Electra
slashing_penalty = MIN_SLASHING_PENALTY
decrease_balance(state, slashed_index, slashing_penalty)
... ...
```
### Impact of modifying correlation penalty
1. Modified `process_slashings()`
```python=
def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei:
"""
Return the combined effective balance of the ``indices``.
``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
Math safe up to ~10B ETH, after which this overflows uint64.
"""
return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices])))
def get_total_active_balance(state: BeaconState) -> Gwei:
"""
Return the combined effective balance of the active validators.
Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
"""
return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state))))
def process_slashings(state: BeaconState) -> None:
epoch = get_current_epoch(state)
total_balance = get_total_active_balance(state)
adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance)
for index, validator in enumerate(state.validators):
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow
# existing logic
#penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
#penalty = penalty_numerator // total_balance * increment
# TODO
penalty_numerator = validator.effective_balance**2 // increment * adjusted_total_slashing_balance
penalty_numerator *= PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX
penalty = penalty_numerator // total_balance**2 * increment
decrease_balance(state, ValidatorIndex(index), penalty)
```
Other Changes to the Spec:
==========================
Impact of modify Execution Payload
----------------------------------
1. Modified `ExecutionPayload()`
```python=
class ExecutionPayload(Container):
# Execution block header fields
parent_hash: Hash32
fee_recipient: ExecutionAddress
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
prev_randao: Bytes32
block_number: uint64
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD]
blob_gas_used: uint64
excess_blob_gas: uint64
deposit_requests: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP6110]
# [New in Electra:EIP7002:EIP7251]
withdrawal_requests: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD]
# [New in Electra:EIP7251]
consolidation_requests: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD]
```
1. Modified `ExecutionPayloadHeader()`
```python=
class ExecutionPayloadHeader(Container):
# Execution block header fields
parent_hash: Hash32
fee_recipient: ExecutionAddress
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
prev_randao: Bytes32
block_number: uint64
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32
transactions_root: Root
withdrawals_root: Root
blob_gas_used: uint64
excess_blob_gas: uint64
deposit_requests_root: Root # [New in Electra:EIP6110]
withdrawal_requests_root: Root # [New in Electra:EIP7002:EIP7251]
consolidation_requests_root: Root # [New in Electra:EIP7251]
```
1. MODIFIED `PROCESS_EXECUTION_PAYLOAD`[¶](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#modified-process_execution_payload)
*Note*: The function `process_execution_payload` is modified to use the new `ExecutionPayloadHeader` type.
```python=
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
payload = body.execution_payload
# Verify consistency of the parent hash with respect to the previous execution payload header
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
# Verify prev_randao
assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state))
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify commitments are under limit
assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
# Verify the execution payload is valid
versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments]
assert execution_engine.verify_and_notify_new_payload(
NewPayloadRequest(
execution_payload=payload,
versioned_hashes=versioned_hashes,
parent_beacon_block_root=state.latest_block_header.parent_root,
)
)
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=payload.parent_hash,
fee_recipient=payload.fee_recipient,
state_root=payload.state_root,
receipts_root=payload.receipts_root,
logs_bloom=payload.logs_bloom,
prev_randao=payload.prev_randao,
block_number=payload.block_number,
gas_limit=payload.gas_limit,
gas_used=payload.gas_used,
timestamp=payload.timestamp,
extra_data=payload.extra_data,
base_fee_per_gas=payload.base_fee_per_gas,
block_hash=payload.block_hash,
transactions_root=hash_tree_root(payload.transactions),
withdrawals_root=hash_tree_root(payload.withdrawals),
blob_gas_used=payload.blob_gas_used,
excess_blob_gas=payload.excess_blob_gas,
deposit_requests_root=hash_tree_root(payload.deposit_requests), # [New in Electra:EIP6110]
withdrawal_requests_root=hash_tree_root(payload.withdrawal_requests), # [New in Electra:EIP7002:EIP7251]
consolidation_requests_root=hash_tree_root(payload.consolidation_requests), # [New in Electra:EIP7251]
)
```
Impact of modifying process_epoch
---------------------------------
1. Updated `process_epoch()`
```python=
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_inactivity_updates(state)
process_rewards_and_penalties(state)
process_registry_updates(state) # [Modified in Electra:EIP7251]
process_slashings(state)
process_eth1_data_reset(state)
process_pending_balance_deposits(state) # [New in Electra:EIP7251]
process_pending_consolidations(state) # [New in Electra:EIP7251]
process_effective_balance_updates(state) # [Modified in Electra:EIP7251]
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_summaries_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
```
1. MODIFIED `PROCESS_OPERATIONS`[¶](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#modified-process_operations), *Note*: The function `process_operations` is modified to support all of the new functionality in Electra.
```python=
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
# [Modified in Electra:EIP6110]
# Disable former deposit mechanism once all prior deposits are processed
eth1_deposit_index_limit = min(state.eth1_data.deposit_count, state.deposit_requests_start_index)
if state.eth1_deposit_index < eth1_deposit_index_limit:
assert len(body.deposits) == min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index)
else:
assert len(body.deposits) == 0
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
for_ops(body.attestations, process_attestation) # [Modified in Electra:EIP7549]
for_ops(body.deposits, process_deposit) # [Modified in Electra:EIP7251]
for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251]
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
for_ops(body.execution_payload.deposit_requests, process_deposit_request) # [New in Electra:EIP6110]
# [New in Electra:EIP7002:EIP7251]
for_ops(body.execution_payload.withdrawal_requests, process_withdrawal_request)
# [New in Electra:EIP7251]
for_ops(body.execution_payload.consolidation_requests, process_consolidation_request)
```
Impact of modifying BeaconState
-------------------------------
```python=
class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT]
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT]
# Sync
current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee
# Execution
latest_execution_payload_header: ExecutionPayloadHeader # [Modified in Electra:EIP6110:EIP7002]
# Withdrawals
next_withdrawal_index: WithdrawalIndex
next_withdrawal_validator_index: ValidatorIndex
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]
deposit_requests_start_index: uint64 # [New in Electra:EIP6110]
deposit_balance_to_consume: Gwei # [New in Electra:EIP7251]
exit_balance_to_consume: Gwei # [New in Electra:EIP7251]
earliest_exit_epoch: Epoch # [New in Electra:EIP7251]
consolidation_balance_to_consume: Gwei # [New in Electra:EIP7251]
earliest_consolidation_epoch: Epoch # [New in Electra:EIP7251]
pending_balance_deposits: List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT] # [New in Electra:EIP7251]
# [New in Electra:EIP7251]
pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]
pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] # [New in Electra:EIP7251]
```
Impact of Testing
-----------------
1. *Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Electra testing only. Modifications include:
2. Use `ELECTRA_FORK_VERSION` as the previous and current fork version.
3. Utilize the Electra `BeaconBlockBody` when constructing the initial `latest_block_header`.
4. *[New in Electra:EIP6110]* Add `deposit_requests_start_index` variable to the genesis state initialization.
5. *[New in Electra:EIP7251]* Initialize new fields to support increasing the maximum effective balance.
```python=
def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32,
eth1_timestamp: uint64,
deposits: Sequence[Deposit],
execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader()
) -> BeaconState:
fork = Fork(
previous_version=ELECTRA_FORK_VERSION, # [Modified in Electra:EIP6110] for testing only
current_version=ELECTRA_FORK_VERSION, # [Modified in Electra:EIP6110]
epoch=GENESIS_EPOCH,
)
state = BeaconState(
genesis_time=eth1_timestamp + GENESIS_DELAY,
fork=fork,
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))),
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy
deposit_requests_start_index=UNSET_DEPOSIT_REQUESTS_START_INDEX, # [New in Electra:EIP6110]
)
# Process deposits
leaves = list(map(lambda deposit: deposit.data, deposits))
for index, deposit in enumerate(deposits):
deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1])
state.eth1_data.deposit_root = hash_tree_root(deposit_data_list)
process_deposit(state, deposit)
# Process deposit balance updates
for deposit in state.pending_balance_deposits:
increase_balance(state, deposit.index, deposit.amount)
state.pending_balance_deposits = []
# Process activations
for index, validator in enumerate(state.validators):
balance = state.balances[index]
# [Modified in Electra:EIP7251]
validator.effective_balance = min(
balance - balance % EFFECTIVE_BALANCE_INCREMENT, get_validator_max_effective_balance(validator))
if validator.effective_balance >= MIN_ACTIVATION_BALANCE:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH
# Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = hash_tree_root(state.validators)
# Fill in sync committees
# Note: A duplicate committee is assigned for the current and next committee at genesis
state.current_sync_committee = get_next_sync_committee(state)
state.next_sync_committee = get_next_sync_committee(state)
# Initialize the execution payload header
state.latest_execution_payload_header = execution_payload_header
return state
```
Summary of TODO:
================
1. Feature 1, 2, 3, 4 are fully implemented in the Electra Spec.
2. Feature 5 is partially implemented, except set custom ceiling for partially withdrawal.
3. Feature 6 is proposed, and needs to be implemented into the spec.
4. Cross check test cases in Execution Layer and Consensus Layer.