Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion specs/_features/eip7805/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def prepare_execution_payload(
parent_hash = state.latest_execution_payload_header.block_hash

# Set the forkchoice head and initiate the payload build process
withdrawals, _ = get_expected_withdrawals(state)
withdrawals, _, _ = get_expected_withdrawals(state)

# [New in EIP7805]
# Get the inclusion list store
Expand Down
86 changes: 63 additions & 23 deletions specs/capella/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
- [Epoch processing](#epoch-processing)
- [Historical summaries updates](#historical-summaries-updates)
- [Block processing](#block-processing)
- [New `get_balance_after_withdrawals`](#new-get_balance_after_withdrawals)
- [New `get_sweep_withdrawals`](#new-get_sweep_withdrawals)
- [New `get_expected_withdrawals`](#new-get_expected_withdrawals)
- [New `process_withdrawals`](#new-process_withdrawals)
- [Modified `process_execution_payload`](#modified-process_execution_payload)
Expand Down Expand Up @@ -336,18 +338,45 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_sync_aggregate(state, block.body.sync_aggregate)
```

#### New `get_expected_withdrawals`
#### New `get_balance_after_withdrawals`

```python
def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]:
epoch = get_current_epoch(state)
withdrawal_index = state.next_withdrawal_index
validator_index = state.next_withdrawal_validator_index
def get_balance_after_withdrawals(
state: BeaconState,
validator_index: ValidatorIndex,
withdrawals: Sequence[Withdrawal],
) -> Gwei:
withdrawn = sum(
withdrawal.amount
for withdrawal in withdrawals
if withdrawal.validator_index == validator_index
)
return state.balances[validator_index] - withdrawn
```

#### New `get_sweep_withdrawals`

```python
def get_sweep_withdrawals(
state: BeaconState,
withdrawal_index: WithdrawalIndex,
validator_index: ValidatorIndex,
epoch: Epoch,
prior_withdrawals: Sequence[Withdrawal],
) -> Tuple[Sequence[Withdrawal], WithdrawalIndex, uint64]:
validators_limit = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
withdrawals_limit = MAX_WITHDRAWALS_PER_PAYLOAD

processed_count: uint64 = 0
withdrawals: List[Withdrawal] = []
bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
for _ in range(bound):
for _ in range(validators_limit):
all_withdrawals = prior_withdrawals + withdrawals
has_reached_limit = len(all_withdrawals) == withdrawals_limit
if has_reached_limit:
break

validator = state.validators[validator_index]
balance = state.balances[validator_index]
balance = get_balance_after_withdrawals(state, validator_index, all_withdrawals)
if is_fully_withdrawable_validator(validator, balance, epoch):
withdrawals.append(
Withdrawal(
Expand All @@ -368,17 +397,36 @@ def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]:
)
)
withdrawal_index += WithdrawalIndex(1)
if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
break

validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
return withdrawals
processed_count += 1

return withdrawals, withdrawal_index, processed_count
```

#### New `get_expected_withdrawals`

```python
def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]:
epoch = get_current_epoch(state)
withdrawal_index = state.next_withdrawal_index
validator_index = state.next_withdrawal_validator_index
withdrawals: List[Withdrawal] = []

# Get sweep withdrawals
sweep_withdrawals, withdrawal_index, processed_validators_sweep_count = get_sweep_withdrawals(
state, withdrawal_index, validator_index, epoch, withdrawals
)
withdrawals.extend(sweep_withdrawals)

return withdrawals, processed_validators_sweep_count
```

#### New `process_withdrawals`

```python
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
expected_withdrawals = get_expected_withdrawals(state)
expected_withdrawals, processed_validators_sweep_count = get_expected_withdrawals(state)
assert payload.withdrawals == expected_withdrawals

for withdrawal in expected_withdrawals:
Expand All @@ -390,17 +438,9 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
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
next_index = state.next_withdrawal_validator_index + processed_validators_sweep_count
next_validator_index = ValidatorIndex(next_index % len(state.validators))
state.next_withdrawal_validator_index = next_validator_index
```

#### Modified `process_execution_payload`
Expand Down
5 changes: 4 additions & 1 deletion specs/capella/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,15 @@ def prepare_execution_payload(
parent_hash = state.latest_execution_payload_header.block_hash

# Set the forkchoice head and initiate the payload build process
# [New in Capella]
withdrawals, _ = get_expected_withdrawals(state)

payload_attributes = PayloadAttributes(
timestamp=compute_time_at_slot(state, state.slot),
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
# [New in Capella]
withdrawals=get_expected_withdrawals(state),
withdrawals=withdrawals,
)
return execution_engine.notify_forkchoice_updated(
head_block_hash=parent_hash,
Expand Down
4 changes: 3 additions & 1 deletion specs/deneb/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,13 @@ def prepare_execution_payload(
parent_hash = state.latest_execution_payload_header.block_hash

# Set the forkchoice head and initiate the payload build process
withdrawals, _ = get_expected_withdrawals(state)

payload_attributes = PayloadAttributes(
timestamp=compute_time_at_slot(state, state.slot),
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
withdrawals=get_expected_withdrawals(state),
withdrawals=withdrawals,
# [New in Deneb:EIP4788]
parent_beacon_block_root=hash_tree_root(state.latest_block_header),
)
Expand Down
162 changes: 110 additions & 52 deletions specs/electra/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
- [New `has_execution_withdrawal_credential`](#new-has_execution_withdrawal_credential)
- [Modified `is_fully_withdrawable_validator`](#modified-is_fully_withdrawable_validator)
- [Modified `is_partially_withdrawable_validator`](#modified-is_partially_withdrawable_validator)
- [New `is_eligible_for_partial_withdrawals`](#new-is_eligible_for_partial_withdrawals)
- [Misc](#misc-1)
- [New `get_committee_indices`](#new-get_committee_indices)
- [New `get_max_effective_balance`](#new-get_max_effective_balance)
Expand Down Expand Up @@ -79,6 +80,8 @@
- [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload)
- [Block processing](#block-processing)
- [Withdrawals](#withdrawals)
- [New `get_pending_partial_withdrawals`](#new-get_pending_partial_withdrawals)
- [Modified `get_sweep_withdrawals`](#modified-get_sweep_withdrawals)
- [Modified `get_expected_withdrawals`](#modified-get_expected_withdrawals)
- [Modified `process_withdrawals`](#modified-process_withdrawals)
- [Execution payload](#execution-payload)
Expand Down Expand Up @@ -545,6 +548,22 @@ def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) ->
)
```

#### New `is_eligible_for_partial_withdrawals`

```python
def is_eligible_for_partial_withdrawals(validator: Validator, balance: Gwei) -> bool:
"""
Check if ``validator`` can process a pending partial withdrawal.
"""
has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
has_excess_balance = balance > MIN_ACTIVATION_BALANCE
return (
validator.exit_epoch == FAR_FUTURE_EPOCH
and has_sufficient_effective_balance
and has_excess_balance
)
Comment on lines +554 to +564
Copy link
Member Author

Choose a reason for hiding this comment

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

In a follow up PR, I would like to rename:

  • is_partially_withdrawable_validator -> has_withdrawable_excess_balance
  • is_eligible_for_partial_withdrawals -> is_partially_withdrawable_validator

The "partially withdrawable" function is very misleading now.

```

### Misc

#### New `get_committee_indices`
Expand Down Expand Up @@ -1183,59 +1202,75 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:

#### Withdrawals

##### Modified `get_expected_withdrawals`

*Note*: The function `get_expected_withdrawals` is modified to support EIP7251.
##### New `get_pending_partial_withdrawals`

```python
def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]:
epoch = get_current_epoch(state)
withdrawal_index = state.next_withdrawal_index
validator_index = state.next_withdrawal_validator_index
withdrawals: List[Withdrawal] = []
processed_partial_withdrawals_count = 0
def get_pending_partial_withdrawals(
state: BeaconState,
withdrawal_index: WithdrawalIndex,
epoch: Epoch,
prior_withdrawals: Sequence[Withdrawal],
) -> Tuple[Sequence[Withdrawal], WithdrawalIndex, uint64]:
withdrawals_limit = min(
len(prior_withdrawals) + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
MAX_WITHDRAWALS_PER_PAYLOAD - 1,
)

# [New in Electra:EIP7251]
# Consume pending partial withdrawals
processed_count = 0
withdrawals: List[Withdrawal] = []
for withdrawal in state.pending_partial_withdrawals:
if (
withdrawal.withdrawable_epoch > epoch
or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP
):
all_withdrawals = prior_withdrawals + withdrawals
is_withdrawable = withdrawal.withdrawable_epoch <= epoch
has_reached_limit = len(all_withdrawals) == withdrawals_limit
if not is_withdrawable or has_reached_limit:
break

validator = state.validators[withdrawal.validator_index]
has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
total_withdrawn = sum(
w.amount for w in withdrawals if w.validator_index == withdrawal.validator_index
)
balance = state.balances[withdrawal.validator_index] - total_withdrawn
has_excess_balance = balance > MIN_ACTIVATION_BALANCE
if (
validator.exit_epoch == FAR_FUTURE_EPOCH
and has_sufficient_effective_balance
and has_excess_balance
):
withdrawable_balance = min(balance - MIN_ACTIVATION_BALANCE, withdrawal.amount)
validator_index = withdrawal.validator_index
validator = state.validators[validator_index]
balance = get_balance_after_withdrawals(state, validator_index, all_withdrawals)
if is_eligible_for_partial_withdrawals(validator, balance):
withdrawal_amount = min(balance - MIN_ACTIVATION_BALANCE, withdrawal.amount)
withdrawals.append(
Withdrawal(
index=withdrawal_index,
validator_index=withdrawal.validator_index,
validator_index=validator_index,
address=ExecutionAddress(validator.withdrawal_credentials[12:]),
amount=withdrawable_balance,
amount=withdrawal_amount,
)
)
withdrawal_index += WithdrawalIndex(1)

processed_partial_withdrawals_count += 1
processed_count += 1

return withdrawals, withdrawal_index, processed_count
```

##### Modified `get_sweep_withdrawals`

*Note*: The function `get_sweep_withdrawals` is modified to use
`get_max_effective_balance`.

```python
def get_sweep_withdrawals(
state: BeaconState,
withdrawal_index: WithdrawalIndex,
validator_index: ValidatorIndex,
epoch: Epoch,
prior_withdrawals: Sequence[Withdrawal],
) -> Tuple[Sequence[Withdrawal], WithdrawalIndex, uint64]:
validators_limit = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
withdrawals_limit = MAX_WITHDRAWALS_PER_PAYLOAD

processed_count: uint64 = 0
withdrawals: List[Withdrawal] = []
for _ in range(validators_limit):
all_withdrawals = prior_withdrawals + withdrawals
has_reached_limit = len(all_withdrawals) == withdrawals_limit
if has_reached_limit:
break

# Sweep for remaining.
bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
for _ in range(bound):
validator = state.validators[validator_index]
# [Modified in Electra:EIP7251]
total_withdrawn = sum(w.amount for w in withdrawals if w.validator_index == validator_index)
balance = state.balances[validator_index] - total_withdrawn
balance = get_balance_after_withdrawals(state, validator_index, all_withdrawals)
if is_fully_withdrawable_validator(validator, balance, epoch):
withdrawals.append(
Withdrawal(
Expand All @@ -1257,10 +1292,39 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal],
)
)
withdrawal_index += WithdrawalIndex(1)
if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
break

validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
return withdrawals, processed_partial_withdrawals_count
processed_count += 1

return withdrawals, withdrawal_index, processed_count
```

##### Modified `get_expected_withdrawals`

*Note*: The function `get_expected_withdrawals` is modified to support EIP7251.

```python
def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64, uint64]:
epoch = get_current_epoch(state)
withdrawal_index = state.next_withdrawal_index
validator_index = state.next_withdrawal_validator_index
withdrawals: List[Withdrawal] = []

# [New in Electra:EIP7251]
# Get partial withdrawals
partial_withdrawals, withdrawal_index, processed_partial_withdrawals_count = (
get_pending_partial_withdrawals(state, withdrawal_index, epoch, withdrawals)
)
withdrawals.extend(partial_withdrawals)

# Get sweep withdrawals
sweep_withdrawals, withdrawal_index, processed_validators_sweep_count = get_sweep_withdrawals(
state, withdrawal_index, validator_index, epoch, withdrawals
)
withdrawals.extend(sweep_withdrawals)

# [Modified in Electra:EIP7251]
return withdrawals, processed_partial_withdrawals_count, processed_validators_sweep_count
```

##### Modified `process_withdrawals`
Expand All @@ -1270,7 +1334,9 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal],
```python
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
# [Modified in Electra:EIP7251]
expected_withdrawals, processed_partial_withdrawals_count = get_expected_withdrawals(state)
expected_withdrawals, processed_partial_withdrawals_count, processed_validators_sweep_count = (
get_expected_withdrawals(state)
)

assert payload.withdrawals == expected_withdrawals

Expand All @@ -1289,17 +1355,9 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
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
next_index = state.next_withdrawal_validator_index + processed_validators_sweep_count
next_validator_index = ValidatorIndex(next_index % len(state.validators))
state.next_withdrawal_validator_index = next_validator_index
```

#### Execution payload
Expand Down
Loading