mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-02-19 22:08:13 +00:00
Partial withdrawals (#2862)
* t push base design for partial withdrawals * moor tests * clean up withdrawals naming * make partial withdrawal randomized tests better * Apply suggestions from code review Co-authored-by: Alex Stokes <r.alex.stokes@gmail.com> Co-authored-by: Hsiao-Wei Wang <hsiaowei.eth@gmail.com> * fix mainnet brokn test * name swap * lint Co-authored-by: Alex Stokes <r.alex.stokes@gmail.com> Co-authored-by: Hsiao-Wei Wang <hsiaowei.eth@gmail.com>
This commit is contained in:
parent
1113aa608f
commit
74489d5523
@ -1 +1,24 @@
|
||||
# Minimal preset - Capella
|
||||
# Mainnet preset - Capella
|
||||
|
||||
# Misc
|
||||
# ---------------------------------------------------------------
|
||||
# 2**8 (= 256) withdrawals
|
||||
MAX_PARTIAL_WITHDRAWALS_PER_EPOCH: 256
|
||||
|
||||
|
||||
# State list lengths
|
||||
# ---------------------------------------------------------------
|
||||
# 2**40 (= 1,099,511,627,776) withdrawals
|
||||
WITHDRAWALS_QUEUE_LIMIT: 1099511627776
|
||||
|
||||
|
||||
# Max operations per block
|
||||
# ---------------------------------------------------------------
|
||||
# 2**4 (= 16)
|
||||
MAX_BLS_TO_EXECUTION_CHANGES: 16
|
||||
|
||||
|
||||
# Execution
|
||||
# ---------------------------------------------------------------
|
||||
# 2**4 (= 16) withdrawals
|
||||
MAX_WITHDRAWALS_PER_PAYLOAD: 16
|
||||
|
@ -1 +1,24 @@
|
||||
# Minimal preset - Capella
|
||||
|
||||
# Misc
|
||||
# ---------------------------------------------------------------
|
||||
# [customized] 16 for more interesting tests at low validator count
|
||||
MAX_PARTIAL_WITHDRAWALS_PER_EPOCH: 16
|
||||
|
||||
|
||||
# State list lengths
|
||||
# ---------------------------------------------------------------
|
||||
# 2**40 (= 1,099,511,627,776) withdrawals
|
||||
WITHDRAWALS_QUEUE_LIMIT: 1099511627776
|
||||
|
||||
|
||||
# Max operations per block
|
||||
# ---------------------------------------------------------------
|
||||
# 2**4 (= 16)
|
||||
MAX_BLS_TO_EXECUTION_CHANGES: 16
|
||||
|
||||
|
||||
# Execution
|
||||
# ---------------------------------------------------------------
|
||||
# [customized] Lower than MAX_PARTIAL_WITHDRAWALS_PER_EPOCH so not all processed in one block
|
||||
MAX_WITHDRAWALS_PER_PAYLOAD: 16
|
||||
|
@ -11,6 +11,7 @@
|
||||
- [Constants](#constants)
|
||||
- [Domain types](#domain-types)
|
||||
- [Preset](#preset)
|
||||
- [Misc](#misc)
|
||||
- [State list lengths](#state-list-lengths)
|
||||
- [Max operations per block](#max-operations-per-block)
|
||||
- [Execution](#execution)
|
||||
@ -30,10 +31,13 @@
|
||||
- [Beacon state mutators](#beacon-state-mutators)
|
||||
- [`withdraw`](#withdraw)
|
||||
- [Predicates](#predicates)
|
||||
- [`has_eth1_withdrawal_credential`](#has_eth1_withdrawal_credential)
|
||||
- [`is_fully_withdrawable_validator`](#is_fully_withdrawable_validator)
|
||||
- [`is_partially_withdrawable_validator`](#is_partially_withdrawable_validator)
|
||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||
- [Epoch processing](#epoch-processing)
|
||||
- [Withdrawals](#withdrawals)
|
||||
- [Full withdrawals](#full-withdrawals)
|
||||
- [Partial withdrawals](#partial-withdrawals)
|
||||
- [Block processing](#block-processing)
|
||||
- [New `process_withdrawals`](#new-process_withdrawals)
|
||||
- [Modified `process_execution_payload`](#modified-process_execution_payload)
|
||||
@ -48,7 +52,8 @@
|
||||
Capella is a consensus-layer upgrade containing a number of features related
|
||||
to validator withdrawals. Including:
|
||||
* Automatic withdrawals of `withdrawable` validators
|
||||
* Partial withdrawals during block proposal
|
||||
* Partial withdrawals sweep for validators with 0x01 withdrawal
|
||||
credentials and balances in exceess of `MAX_EFFECTIVE_BALANCE`
|
||||
* Operation to change from `BLS_WITHDRAWAL_PREFIX` to
|
||||
`ETH1_ADDRESS_WITHDRAWAL_PREFIX` versioned withdrawal credentials to enable withdrawals for a validator
|
||||
|
||||
@ -70,11 +75,17 @@ We define the following Python custom types for type hinting and readability:
|
||||
|
||||
## Preset
|
||||
|
||||
### Misc
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `MAX_PARTIAL_WITHDRAWALS_PER_EPOCH` | `uint64(2**8)` (= 256) |
|
||||
|
||||
### State list lengths
|
||||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `WITHDRAWALS_QUEUE_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawals enqueued in state|
|
||||
| `WITHDRAWAL_QUEUE_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawals enqueued in state|
|
||||
|
||||
### Max operations per block
|
||||
|
||||
@ -245,8 +256,9 @@ class BeaconState(Container):
|
||||
# Execution
|
||||
latest_execution_payload_header: ExecutionPayloadHeader
|
||||
# Withdrawals
|
||||
withdrawal_index: WithdrawalIndex
|
||||
withdrawals_queue: List[Withdrawal, WITHDRAWALS_QUEUE_LIMIT] # [New in Capella]
|
||||
withdrawal_queue: List[Withdrawal, WITHDRAWAL_QUEUE_LIMIT] # [New in Capella]
|
||||
next_withdrawal_index: WithdrawalIndex # [New in Capella]
|
||||
next_partial_withdrawal_validator_index: ValidatorIndex # [New in Capella]
|
||||
```
|
||||
|
||||
## Helpers
|
||||
@ -261,16 +273,26 @@ def withdraw_balance(state: BeaconState, index: ValidatorIndex, amount: Gwei) ->
|
||||
decrease_balance(state, index, amount)
|
||||
# Create a corresponding withdrawal receipt
|
||||
withdrawal = Withdrawal(
|
||||
index=state.withdrawal_index,
|
||||
address=state.validators[index].withdrawal_credentials[12:],
|
||||
index=state.next_withdrawal_index,
|
||||
address=ExecutionAddress(state.validators[index].withdrawal_credentials[12:]),
|
||||
amount=amount,
|
||||
)
|
||||
state.withdrawal_index = WithdrawalIndex(state.withdrawal_index + 1)
|
||||
state.withdrawals_queue.append(withdrawal)
|
||||
state.next_withdrawal_index = WithdrawalIndex(state.next_withdrawal_index + 1)
|
||||
state.withdrawal_queue.append(withdrawal)
|
||||
```
|
||||
|
||||
### Predicates
|
||||
|
||||
#### `has_eth1_withdrawal_credential`
|
||||
|
||||
```python
|
||||
def has_eth1_withdrawal_credential(validator: Validator) -> bool:
|
||||
"""
|
||||
Check if ``validator`` has an 0x01 prefixed "eth1" withdrawal credential
|
||||
"""
|
||||
return validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||
```
|
||||
|
||||
#### `is_fully_withdrawable_validator`
|
||||
|
||||
```python
|
||||
@ -278,8 +300,22 @@ def is_fully_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool:
|
||||
"""
|
||||
Check if ``validator`` is fully withdrawable.
|
||||
"""
|
||||
is_eth1_withdrawal_prefix = validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||
return is_eth1_withdrawal_prefix and validator.withdrawable_epoch <= epoch < validator.fully_withdrawn_epoch
|
||||
return (
|
||||
has_eth1_withdrawal_credential(validator)
|
||||
and validator.withdrawable_epoch <= epoch < validator.fully_withdrawn_epoch
|
||||
)
|
||||
```
|
||||
|
||||
#### `is_partially_withdrawable_validator`
|
||||
|
||||
```python
|
||||
def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> bool:
|
||||
"""
|
||||
Check if ``validator`` is partially withdrawable.
|
||||
"""
|
||||
has_max_effective_balance = validator.effective_balance == MAX_EFFECTIVE_BALANCE
|
||||
has_excess_balance = balance > MAX_EFFECTIVE_BALANCE
|
||||
return has_eth1_withdrawal_credential(validator) and has_max_effective_balance and has_excess_balance
|
||||
```
|
||||
|
||||
## Beacon chain state transition function
|
||||
@ -301,9 +337,11 @@ def process_epoch(state: BeaconState) -> None:
|
||||
process_participation_flag_updates(state)
|
||||
process_sync_committee_updates(state)
|
||||
process_full_withdrawals(state) # [New in Capella]
|
||||
process_partial_withdrawals(state) # [New in Capella]
|
||||
|
||||
```
|
||||
|
||||
#### Withdrawals
|
||||
#### Full withdrawals
|
||||
|
||||
*Note*: The function `process_full_withdrawals` is new.
|
||||
|
||||
@ -317,6 +355,31 @@ def process_full_withdrawals(state: BeaconState) -> None:
|
||||
validator.fully_withdrawn_epoch = current_epoch
|
||||
```
|
||||
|
||||
#### Partial withdrawals
|
||||
|
||||
*Note*: The function `process_partial_withdrawals` is new.
|
||||
|
||||
```python
|
||||
def process_partial_withdrawals(state: BeaconState) -> None:
|
||||
partial_withdrawals_count = 0
|
||||
# Begin where we left off last time
|
||||
validator_index = state.next_partial_withdrawal_validator_index
|
||||
for _ in range(len(state.validators)):
|
||||
balance = state.balances[validator_index]
|
||||
validator = state.validators[validator_index]
|
||||
if is_partially_withdrawable_validator(validator, balance):
|
||||
withdraw_balance(state, validator_index, balance - MAX_EFFECTIVE_BALANCE)
|
||||
partial_withdrawals_count += 1
|
||||
|
||||
# Iterate to next validator to check for partial withdrawal
|
||||
validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
|
||||
# Exit if performed maximum allowable withdrawals
|
||||
if partial_withdrawals_count == MAX_PARTIAL_WITHDRAWALS_PER_EPOCH:
|
||||
break
|
||||
|
||||
state.next_partial_withdrawal_validator_index = validator_index
|
||||
```
|
||||
|
||||
### Block processing
|
||||
|
||||
```python
|
||||
@ -335,15 +398,15 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
||||
|
||||
```python
|
||||
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
|
||||
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue))
|
||||
dequeued_withdrawals = state.withdrawals_queue[:num_withdrawals]
|
||||
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue))
|
||||
dequeued_withdrawals = state.withdrawal_queue[:num_withdrawals]
|
||||
|
||||
assert len(dequeued_withdrawals) == len(payload.withdrawals)
|
||||
for dequeued_withdrawal, withdrawal in zip(dequeued_withdrawals, payload.withdrawals):
|
||||
assert dequeued_withdrawal == withdrawal
|
||||
|
||||
# Remove dequeued withdrawals from state
|
||||
state.withdrawals_queue = state.withdrawals_queue[num_withdrawals:]
|
||||
state.withdrawal_queue = state.withdrawal_queue[num_withdrawals:]
|
||||
```
|
||||
|
||||
#### Modified `process_execution_payload`
|
||||
|
@ -89,8 +89,9 @@ def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState:
|
||||
# Execution-layer
|
||||
latest_execution_payload_header=pre.latest_execution_payload_header,
|
||||
# Withdrawals
|
||||
withdrawal_index=WithdrawalIndex(0),
|
||||
withdrawals_queue=[],
|
||||
withdrawal_queue=[],
|
||||
next_withdrawal_index=WithdrawalIndex(0),
|
||||
next_partial_withdrawal_validator_index=ValidatorIndex(0),
|
||||
)
|
||||
|
||||
for pre_validator in pre.validators:
|
||||
|
@ -61,8 +61,8 @@ helper `get_expected_withdrawals`) and passed into the `ExecutionEngine` within
|
||||
|
||||
```python
|
||||
def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]:
|
||||
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue))
|
||||
return state.withdrawals_queue[:num_withdrawals]
|
||||
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue))
|
||||
return state.withdrawal_queue[:num_withdrawals]
|
||||
```
|
||||
|
||||
*Note*: The only change made to `prepare_execution_payload` is to call
|
||||
|
@ -7,8 +7,8 @@ from eth2spec.test.context import spec_state_test, expect_assertion_error, with_
|
||||
from eth2spec.test.helpers.state import next_slot
|
||||
|
||||
|
||||
def prepare_withdrawals_queue(spec, state, num_withdrawals):
|
||||
pre_queue_len = len(state.withdrawals_queue)
|
||||
def prepare_withdrawal_queue(spec, state, num_withdrawals):
|
||||
pre_queue_len = len(state.withdrawal_queue)
|
||||
|
||||
for i in range(num_withdrawals):
|
||||
withdrawal = spec.Withdrawal(
|
||||
@ -16,9 +16,9 @@ def prepare_withdrawals_queue(spec, state, num_withdrawals):
|
||||
address=b'\x42' * 20,
|
||||
amount=200000 + i,
|
||||
)
|
||||
state.withdrawals_queue.append(withdrawal)
|
||||
state.withdrawal_queue.append(withdrawal)
|
||||
|
||||
assert len(state.withdrawals_queue) == num_withdrawals + pre_queue_len
|
||||
assert len(state.withdrawal_queue) == num_withdrawals + pre_queue_len
|
||||
|
||||
|
||||
def run_withdrawals_processing(spec, state, execution_payload, valid=True):
|
||||
@ -30,8 +30,8 @@ def run_withdrawals_processing(spec, state, execution_payload, valid=True):
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
|
||||
pre_withdrawals_queue = state.withdrawals_queue.copy()
|
||||
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(pre_withdrawals_queue))
|
||||
pre_withdrawal_queue = state.withdrawal_queue.copy()
|
||||
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(pre_withdrawal_queue))
|
||||
|
||||
yield 'pre', state
|
||||
yield 'execution_payload', execution_payload
|
||||
@ -45,18 +45,18 @@ def run_withdrawals_processing(spec, state, execution_payload, valid=True):
|
||||
|
||||
yield 'post', state
|
||||
|
||||
if len(pre_withdrawals_queue) == 0:
|
||||
assert len(state.withdrawals_queue) == 0
|
||||
elif len(pre_withdrawals_queue) <= num_withdrawals:
|
||||
assert len(state.withdrawals_queue) == 0
|
||||
if len(pre_withdrawal_queue) == 0:
|
||||
assert len(state.withdrawal_queue) == 0
|
||||
elif len(pre_withdrawal_queue) <= num_withdrawals:
|
||||
assert len(state.withdrawal_queue) == 0
|
||||
else:
|
||||
assert state.withdrawals_queue == pre_withdrawals_queue[num_withdrawals:]
|
||||
assert state.withdrawal_queue == pre_withdrawal_queue[num_withdrawals:]
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_empty_queue(spec, state):
|
||||
assert len(state.withdrawals_queue) == 0
|
||||
assert len(state.withdrawal_queue) == 0
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -67,7 +67,7 @@ def test_success_empty_queue(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_one_in_queue(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, 1)
|
||||
prepare_withdrawal_queue(spec, state, 1)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -78,7 +78,7 @@ def test_success_one_in_queue(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_max_per_slot_in_queue(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
|
||||
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -89,7 +89,7 @@ def test_success_max_per_slot_in_queue(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_a_lot_in_queue(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -104,7 +104,7 @@ def test_success_a_lot_in_queue(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_empty_queue_non_empty_withdrawals(spec, state):
|
||||
assert len(state.withdrawals_queue) == 0
|
||||
assert len(state.withdrawal_queue) == 0
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -121,7 +121,7 @@ def test_fail_empty_queue_non_empty_withdrawals(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_one_in_queue_none_in_withdrawals(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, 1)
|
||||
prepare_withdrawal_queue(spec, state, 1)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -133,7 +133,7 @@ def test_fail_one_in_queue_none_in_withdrawals(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_one_in_queue_two_in_withdrawals(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, 1)
|
||||
prepare_withdrawal_queue(spec, state, 1)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -145,7 +145,7 @@ def test_fail_one_in_queue_two_in_withdrawals(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_max_per_slot_in_queue_one_less_in_withdrawals(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
|
||||
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -157,7 +157,7 @@ def test_fail_max_per_slot_in_queue_one_less_in_withdrawals(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_a_lot_in_queue_too_few_in_withdrawals(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -173,7 +173,7 @@ def test_fail_a_lot_in_queue_too_few_in_withdrawals(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_incorrect_dequeue_index(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, 1)
|
||||
prepare_withdrawal_queue(spec, state, 1)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -185,7 +185,7 @@ def test_fail_incorrect_dequeue_index(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_incorrect_dequeue_address(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, 1)
|
||||
prepare_withdrawal_queue(spec, state, 1)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -197,7 +197,7 @@ def test_fail_incorrect_dequeue_address(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_incorrect_dequeue_amount(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, 1)
|
||||
prepare_withdrawal_queue(spec, state, 1)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -209,7 +209,7 @@ def test_fail_incorrect_dequeue_amount(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_one_of_many_dequeued_incorrectly(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
@ -227,7 +227,7 @@ def test_fail_one_of_many_dequeued_incorrectly(spec, state):
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_fail_many_dequeued_incorrectly(spec, state):
|
||||
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
prepare_withdrawal_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
|
||||
|
||||
next_slot(spec, state)
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
|
@ -17,8 +17,8 @@ def set_validator_withdrawable(spec, state, index, withdrawable_epoch=None):
|
||||
|
||||
|
||||
def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None):
|
||||
pre_withdrawal_index = state.withdrawal_index
|
||||
pre_withdrawals_queue = state.withdrawals_queue
|
||||
pre_next_withdrawal_index = state.next_withdrawal_index
|
||||
pre_withdrawal_queue = state.withdrawal_queue.copy()
|
||||
to_be_withdrawn_indices = [
|
||||
index for index, validator in enumerate(state.validators)
|
||||
if spec.is_fully_withdrawable_validator(validator, spec.get_current_epoch(state))
|
||||
@ -26,6 +26,8 @@ def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None):
|
||||
|
||||
if num_expected_withdrawals is not None:
|
||||
assert len(to_be_withdrawn_indices) == num_expected_withdrawals
|
||||
else:
|
||||
num_expected_withdrawals = len(to_be_withdrawn_indices)
|
||||
|
||||
yield from run_epoch_processing_with(spec, state, 'process_full_withdrawals')
|
||||
|
||||
@ -34,8 +36,8 @@ def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None):
|
||||
assert validator.fully_withdrawn_epoch == spec.get_current_epoch(state)
|
||||
assert state.balances[index] == 0
|
||||
|
||||
assert len(state.withdrawals_queue) == len(pre_withdrawals_queue) + num_expected_withdrawals
|
||||
assert state.withdrawal_index == pre_withdrawal_index + num_expected_withdrawals
|
||||
assert len(state.withdrawal_queue) == len(pre_withdrawal_queue) + num_expected_withdrawals
|
||||
assert state.next_withdrawal_index == pre_next_withdrawal_index + num_expected_withdrawals
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@ -65,10 +67,10 @@ def test_single_withdrawal(spec, state):
|
||||
# Make one validator withdrawable
|
||||
set_validator_withdrawable(spec, state, 0)
|
||||
|
||||
assert state.withdrawal_index == 0
|
||||
assert state.next_withdrawal_index == 0
|
||||
yield from run_process_full_withdrawals(spec, state, 1)
|
||||
|
||||
assert state.withdrawal_index == 1
|
||||
assert state.next_withdrawal_index == 1
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
|
@ -0,0 +1,224 @@
|
||||
import random
|
||||
from eth2spec.test.helpers.constants import MINIMAL
|
||||
from eth2spec.test.context import (
|
||||
with_capella_and_later,
|
||||
spec_state_test,
|
||||
with_presets,
|
||||
)
|
||||
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_to
|
||||
from eth2spec.test.helpers.state import next_epoch
|
||||
from eth2spec.test.helpers.random import randomize_state
|
||||
|
||||
|
||||
def set_validator_partially_withdrawable(spec, state, index, rng=random.Random(666)):
|
||||
validator = state.validators[index]
|
||||
validator.withdrawal_credentials = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
|
||||
validator.effective_balance = spec.MAX_EFFECTIVE_BALANCE
|
||||
state.balances[index] = spec.MAX_EFFECTIVE_BALANCE + rng.randint(1, 100000000)
|
||||
|
||||
assert spec.is_partially_withdrawable_validator(validator, state.balances[index])
|
||||
|
||||
|
||||
def run_process_partial_withdrawals(spec, state, num_expected_withdrawals=None):
|
||||
# Run rest of epoch processing before predicting partial withdrawals as
|
||||
# balance changes can affect withdrawability
|
||||
run_epoch_processing_to(spec, state, 'process_partial_withdrawals')
|
||||
|
||||
pre_next_withdrawal_index = state.next_withdrawal_index
|
||||
pre_withdrawal_queue = state.withdrawal_queue.copy()
|
||||
|
||||
partially_withdrawable_indices = [
|
||||
index for index, validator in enumerate(state.validators)
|
||||
if spec.is_partially_withdrawable_validator(validator, state.balances[index])
|
||||
]
|
||||
num_partial_withdrawals = min(len(partially_withdrawable_indices), spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH)
|
||||
|
||||
if num_expected_withdrawals is not None:
|
||||
assert num_partial_withdrawals == num_expected_withdrawals
|
||||
else:
|
||||
num_expected_withdrawals = num_partial_withdrawals
|
||||
|
||||
yield 'pre', state
|
||||
spec.process_partial_withdrawals(state)
|
||||
yield 'post', state
|
||||
|
||||
post_partially_withdrawable_indices = [
|
||||
index for index, validator in enumerate(state.validators)
|
||||
if spec.is_partially_withdrawable_validator(validator, state.balances[index])
|
||||
]
|
||||
|
||||
assert len(partially_withdrawable_indices) - num_partial_withdrawals == len(post_partially_withdrawable_indices)
|
||||
|
||||
assert len(state.withdrawal_queue) == len(pre_withdrawal_queue) + num_expected_withdrawals
|
||||
assert state.next_withdrawal_index == pre_next_withdrawal_index + num_expected_withdrawals
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_no_withdrawable(spec, state):
|
||||
pre_validators = state.validators.copy()
|
||||
yield from run_process_partial_withdrawals(spec, state, 0)
|
||||
|
||||
assert pre_validators == state.validators
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_one_partial_withdrawable(spec, state):
|
||||
validator_index = len(state.validators) // 2
|
||||
set_validator_partially_withdrawable(spec, state, validator_index)
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, 1)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_one_partial_withdrawable_not_yet_active(spec, state):
|
||||
validator_index = len(state.validators) // 2
|
||||
state.validators[validator_index].activation_epoch += 4
|
||||
set_validator_partially_withdrawable(spec, state, validator_index)
|
||||
|
||||
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, 1)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_one_partial_withdrawable_in_exit_queue(spec, state):
|
||||
validator_index = len(state.validators) // 2
|
||||
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state) + 1
|
||||
set_validator_partially_withdrawable(spec, state, validator_index)
|
||||
|
||||
assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
|
||||
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state) + 1)
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, 1)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_one_partial_withdrawable_exited(spec, state):
|
||||
validator_index = len(state.validators) // 2
|
||||
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state)
|
||||
set_validator_partially_withdrawable(spec, state, validator_index)
|
||||
|
||||
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, 1)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_one_partial_withdrawable_active_and_slashed(spec, state):
|
||||
validator_index = len(state.validators) // 2
|
||||
state.validators[validator_index].slashed = True
|
||||
set_validator_partially_withdrawable(spec, state, validator_index)
|
||||
|
||||
assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, 1)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_one_partial_withdrawable_exited_and_slashed(spec, state):
|
||||
validator_index = len(state.validators) // 2
|
||||
state.validators[validator_index].slashed = True
|
||||
state.validators[validator_index].exit_epoch = spec.get_current_epoch(state)
|
||||
set_validator_partially_withdrawable(spec, state, validator_index)
|
||||
|
||||
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, 1)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_two_partial_withdrawable(spec, state):
|
||||
set_validator_partially_withdrawable(spec, state, 0)
|
||||
set_validator_partially_withdrawable(spec, state, 1)
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, 2)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_success_max_partial_withdrawable(spec, state):
|
||||
# Sanity check that this test works for this state
|
||||
assert len(state.validators) >= spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH
|
||||
|
||||
for i in range(spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH):
|
||||
set_validator_partially_withdrawable(spec, state, i)
|
||||
|
||||
yield from run_process_partial_withdrawals(spec, state, spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@with_presets([MINIMAL], reason="not no enough validators with mainnet config")
|
||||
@spec_state_test
|
||||
def test_success_max_plus_one_withdrawable(spec, state):
|
||||
# Sanity check that this test works for this state
|
||||
assert len(state.validators) >= spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH + 1
|
||||
|
||||
# More than MAX_PARTIAL_WITHDRAWALS_PER_EPOCH partially withdrawable
|
||||
for i in range(spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH + 1):
|
||||
set_validator_partially_withdrawable(spec, state, i)
|
||||
|
||||
# Should only have MAX_PARTIAL_WITHDRAWALS_PER_EPOCH withdrawals created
|
||||
yield from run_process_partial_withdrawals(spec, state, spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH)
|
||||
|
||||
|
||||
def run_random_partial_withdrawals_test(spec, state, rng):
|
||||
for _ in range(rng.randint(0, 2)):
|
||||
next_epoch(spec, state)
|
||||
randomize_state(spec, state, rng)
|
||||
|
||||
num_validators = len(state.validators)
|
||||
state.next_partial_withdrawal_validator_index = rng.randint(0, num_validators - 1)
|
||||
|
||||
num_partially_withdrawable = rng.randint(0, num_validators - 1)
|
||||
partially_withdrawable_indices = rng.sample(range(num_validators), num_partially_withdrawable)
|
||||
for index in partially_withdrawable_indices:
|
||||
set_validator_partially_withdrawable(spec, state, index)
|
||||
|
||||
# Note: due to the randomness and other epoch processing, some of these set as "partially withdrawable"
|
||||
# may not be partially withdrawable once we get to ``process_partial_withdrawals``,
|
||||
# thus *not* using the optional third param in this call
|
||||
yield from run_process_partial_withdrawals(spec, state)
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_random_0(spec, state):
|
||||
yield from run_random_partial_withdrawals_test(spec, state, random.Random(0))
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_random_1(spec, state):
|
||||
yield from run_random_partial_withdrawals_test(spec, state, random.Random(1))
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_random_2(spec, state):
|
||||
yield from run_random_partial_withdrawals_test(spec, state, random.Random(2))
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_random_3(spec, state):
|
||||
yield from run_random_partial_withdrawals_test(spec, state, random.Random(3))
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_random_4(spec, state):
|
||||
yield from run_random_partial_withdrawals_test(spec, state, random.Random(4))
|
||||
|
||||
|
||||
@with_capella_and_later
|
||||
@spec_state_test
|
||||
def test_random_5(spec, state):
|
||||
yield from run_random_partial_withdrawals_test(spec, state, random.Random(5))
|
@ -29,6 +29,7 @@ def get_process_calls(spec):
|
||||
),
|
||||
'process_sync_committee_updates', # altair
|
||||
'process_full_withdrawals', # capella
|
||||
'process_partial_withdrawals', # capella
|
||||
# TODO: add sharding processing functions when spec stabilizes.
|
||||
]
|
||||
|
||||
|
@ -29,8 +29,8 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
|
||||
transactions=empty_txs,
|
||||
)
|
||||
if spec.fork not in FORKS_BEFORE_CAPELLA:
|
||||
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue))
|
||||
payload.withdrawals = state.withdrawals_queue[:num_withdrawals]
|
||||
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue))
|
||||
payload.withdrawals = state.withdrawal_queue[:num_withdrawals]
|
||||
|
||||
# TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however.
|
||||
payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user