From 9bf10afb6a2cdc6d20e69c0faf4530056ba1e1d7 Mon Sep 17 00:00:00 2001 From: djrtwo Date: Sun, 16 Oct 2022 16:00:17 -0500 Subject: [PATCH 1/3] rework PR onto current dev from @rolfyone --- presets/mainnet/capella.yaml | 2 +- presets/minimal/capella.yaml | 2 +- specs/capella/beacon-chain.md | 46 +++++++++++++---------------------- specs/capella/fork.md | 2 +- 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/presets/mainnet/capella.yaml b/presets/mainnet/capella.yaml index f04bdd06f4..2c60b95e49 100644 --- a/presets/mainnet/capella.yaml +++ b/presets/mainnet/capella.yaml @@ -3,7 +3,7 @@ # Misc # --------------------------------------------------------------- # 2**8 (= 256) withdrawals -MAX_PARTIAL_WITHDRAWALS_PER_EPOCH: 256 +MAX_WITHDRAWALS_PER_EPOCH: 256 # State list lengths diff --git a/presets/minimal/capella.yaml b/presets/minimal/capella.yaml index 0476172a10..ea1066fa5d 100644 --- a/presets/minimal/capella.yaml +++ b/presets/minimal/capella.yaml @@ -3,7 +3,7 @@ # Misc # --------------------------------------------------------------- # [customized] 16 for more interesting tests at low validator count -MAX_PARTIAL_WITHDRAWALS_PER_EPOCH: 16 +MAX_WITHDRAWALS_PER_EPOCH: 16 # State list lengths diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 09d568f18c..d256330260 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -35,8 +35,7 @@ - [`is_partially_withdrawable_validator`](#is_partially_withdrawable_validator) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Epoch processing](#epoch-processing) - - [Full withdrawals](#full-withdrawals) - - [Partial withdrawals](#partial-withdrawals) + - [Process withdrawals into queue](#process-withdrawals-into-queue) - [Block processing](#block-processing) - [New `process_withdrawals`](#new-process_withdrawals) - [Modified `process_execution_payload`](#modified-process_execution_payload) @@ -79,7 +78,7 @@ We define the following Python custom types for type hinting and readability: | Name | Value | | - | - | -| `MAX_PARTIAL_WITHDRAWALS_PER_EPOCH` | `uint64(2**8)` (= 256) | +| `MAX_WITHDRAWALS_PER_EPOCH` | `uint64(2**8)` (= 256) | ### State list lengths @@ -242,7 +241,7 @@ class BeaconState(Container): # Withdrawals 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] + next_withdrawal_validator_index: ValidatorIndex # [New in Capella] ``` ## Helpers @@ -321,48 +320,37 @@ def process_epoch(state: BeaconState) -> None: process_historical_roots_update(state) process_participation_flag_updates(state) process_sync_committee_updates(state) - process_full_withdrawals(state) # [New in Capella] - process_partial_withdrawals(state) # [New in Capella] + process_withdrawals_into_queue(state) # [New in Capella] ``` -#### Full withdrawals +#### Process withdrawals into queue -*Note*: The function `process_full_withdrawals` is new. +*Note*: The function `process_withdrawals_into_queue` is new. ```python -def process_full_withdrawals(state: BeaconState) -> None: +def process_withdrawals_into_queue(state: BeaconState) -> None: + withdrawals_count = 0 current_epoch = get_current_epoch(state) - for index in range(len(state.validators)): - balance = state.balances[index] - validator = state.validators[index] - if is_fully_withdrawable_validator(validator, balance, current_epoch): - withdraw_balance(state, ValidatorIndex(index), balance) -``` - -#### 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 + validator_index = state.next_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): + if is_fully_withdrawable_validator(validator, balance, current_epoch): + withdraw_balance(state, ValidatorIndex(validator_index), balance) + withdrawals_count += 1 + elif is_partially_withdrawable_validator(validator, balance): withdraw_balance(state, validator_index, balance - MAX_EFFECTIVE_BALANCE) - partial_withdrawals_count += 1 + withdrawals_count += 1 - # Iterate to next validator to check for partial withdrawal + # Iterate to next validator to check for 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: + if withdrawals_count == MAX_WITHDRAWALS_PER_EPOCH: break - state.next_partial_withdrawal_validator_index = validator_index + state.next_withdrawal_validator_index = validator_index ``` ### Block processing diff --git a/specs/capella/fork.md b/specs/capella/fork.md index e6e70f7974..7101345f6a 100644 --- a/specs/capella/fork.md +++ b/specs/capella/fork.md @@ -114,7 +114,7 @@ def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState: # Withdrawals withdrawal_queue=[], next_withdrawal_index=WithdrawalIndex(0), - next_partial_withdrawal_validator_index=ValidatorIndex(0), + next_withdrawal_validator_index=ValidatorIndex(0), ) return post From b452d717166f6d18d909b7700a9b02713894db35 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 17 Oct 2022 17:24:52 -0500 Subject: [PATCH 2/3] Fix withdrawals tests --- presets/minimal/capella.yaml | 2 +- .../test_process_full_withdrawals.py | 174 ------- .../test_process_partial_withdrawals.py | 262 ----------- .../test_process_withdrawals_into_queue.py | 426 ++++++++++++++++++ .../test/capella/sanity/test_blocks.py | 2 +- .../eth2spec/test/helpers/epoch_processing.py | 3 +- 6 files changed, 429 insertions(+), 440 deletions(-) delete mode 100644 tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py delete mode 100644 tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_partial_withdrawals.py create mode 100644 tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py diff --git a/presets/minimal/capella.yaml b/presets/minimal/capella.yaml index ea1066fa5d..301ffb1807 100644 --- a/presets/minimal/capella.yaml +++ b/presets/minimal/capella.yaml @@ -20,5 +20,5 @@ MAX_BLS_TO_EXECUTION_CHANGES: 16 # Execution # --------------------------------------------------------------- -# [customized] Lower than MAX_PARTIAL_WITHDRAWALS_PER_EPOCH so not all processed in one block +# [customized] Lower than MAX_WITHDRAWALS_PER_EPOCH so not all processed in one block MAX_WITHDRAWALS_PER_PAYLOAD: 8 diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py deleted file mode 100644 index 35d2968cb9..0000000000 --- a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py +++ /dev/null @@ -1,174 +0,0 @@ -from random import Random - -from eth2spec.test.context import ( - with_capella_and_later, - spec_state_test, -) -from eth2spec.test.helpers.random import ( - randomize_state, -) -from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_to, -) -from eth2spec.test.helpers.withdrawals import ( - set_validator_fully_withdrawable, -) - - -def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None): - run_epoch_processing_to(spec, state, 'process_full_withdrawals') - - 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, state.balances[index], spec.get_current_epoch(state)) - ] - - 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 'pre', state - spec.process_full_withdrawals(state) - yield 'post', state - - for index in to_be_withdrawn_indices: - assert state.balances[index] == 0 - - 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_no_withdrawable_validators(spec, state): - pre_validators = state.validators.copy() - yield from run_process_full_withdrawals(spec, state, 0) - - assert pre_validators == state.validators - - -@with_capella_and_later -@spec_state_test -def test_withdrawable_epoch_but_0_balance(spec, state): - current_epoch = spec.get_current_epoch(state) - set_validator_fully_withdrawable(spec, state, 0, current_epoch) - - state.validators[0].effective_balance = 10000000000 - state.balances[0] = 0 - - yield from run_process_full_withdrawals(spec, state, 0) - - -@with_capella_and_later -@spec_state_test -def test_withdrawable_epoch_but_0_effective_balance_0_balance(spec, state): - current_epoch = spec.get_current_epoch(state) - set_validator_fully_withdrawable(spec, state, 0, current_epoch) - - state.validators[0].effective_balance = 0 - state.balances[0] = 0 - - yield from run_process_full_withdrawals(spec, state, 0) - - -@with_capella_and_later -@spec_state_test -def test_withdrawable_epoch_but_0_effective_balance_nonzero_balance(spec, state): - current_epoch = spec.get_current_epoch(state) - set_validator_fully_withdrawable(spec, state, 0, current_epoch) - - state.validators[0].effective_balance = 0 - state.balances[0] = 100000000 - - yield from run_process_full_withdrawals(spec, state, 1) - - -@with_capella_and_later -@spec_state_test -def test_no_withdrawals_but_some_next_epoch(spec, state): - current_epoch = spec.get_current_epoch(state) - - # Make a few validators withdrawable at the *next* epoch - for index in range(3): - set_validator_fully_withdrawable(spec, state, index, current_epoch + 1) - - yield from run_process_full_withdrawals(spec, state, 0) - - -@with_capella_and_later -@spec_state_test -def test_single_withdrawal(spec, state): - # Make one validator withdrawable - set_validator_fully_withdrawable(spec, state, 0) - - assert state.next_withdrawal_index == 0 - yield from run_process_full_withdrawals(spec, state, 1) - - assert state.next_withdrawal_index == 1 - - -@with_capella_and_later -@spec_state_test -def test_multi_withdrawal(spec, state): - # Make a few validators withdrawable - for index in range(3): - set_validator_fully_withdrawable(spec, state, index) - - yield from run_process_full_withdrawals(spec, state, 3) - - -@with_capella_and_later -@spec_state_test -def test_all_withdrawal(spec, state): - # Make all validators withdrawable - for index in range(len(state.validators)): - set_validator_fully_withdrawable(spec, state, index) - - yield from run_process_full_withdrawals(spec, state, len(state.validators)) - - -def run_random_full_withdrawals_test(spec, state, rng): - randomize_state(spec, state, rng) - for index in range(len(state.validators)): - # 50% withdrawable - if rng.choice([True, False]): - set_validator_fully_withdrawable(spec, state, index) - validator = state.validators[index] - # 12.5% unset credentials - if rng.randint(0, 7) == 0: - validator.withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] - # 12.5% not enough balance - if rng.randint(0, 7) == 0: - state.balances[index] = 0 - # 12.5% not close enough epoch - if rng.randint(0, 7) == 0: - validator.withdrawable_epoch += 1 - - yield from run_process_full_withdrawals(spec, state, None) - - -@with_capella_and_later -@spec_state_test -def test_random_withdrawals_0(spec, state): - yield from run_random_full_withdrawals_test(spec, state, Random(444)) - - -@with_capella_and_later -@spec_state_test -def test_random_withdrawals_1(spec, state): - yield from run_random_full_withdrawals_test(spec, state, Random(420)) - - -@with_capella_and_later -@spec_state_test -def test_random_withdrawals_2(spec, state): - yield from run_random_full_withdrawals_test(spec, state, Random(200)) - - -@with_capella_and_later -@spec_state_test -def test_random_withdrawals_3(spec, state): - yield from run_random_full_withdrawals_test(spec, state, Random(2000000)) diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_partial_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_partial_withdrawals.py deleted file mode 100644 index 7569d28621..0000000000 --- a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_partial_withdrawals.py +++ /dev/null @@ -1,262 +0,0 @@ -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 -from eth2spec.test.helpers.withdrawals import ( - set_validator_partially_withdrawable, - set_eth1_withdrawal_credential_with_balance, -) - - -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_no_max_effective_balance(spec, state): - validator_index = len(state.validators) // 2 - # To be partially withdrawable, the validator's effective balance must be maxed out - set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE - 1) - validator = state.validators[validator_index] - - assert validator.effective_balance < spec.MAX_EFFECTIVE_BALANCE - assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) - - yield from run_process_partial_withdrawals(spec, state, 0) - - -@with_capella_and_later -@spec_state_test -def test_success_no_excess_balance(spec, state): - validator_index = len(state.validators) // 2 - # To be partially withdrawable, the validator needs an excess balance - set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE) - validator = state.validators[validator_index] - - assert validator.effective_balance == spec.MAX_EFFECTIVE_BALANCE - assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) - - yield from run_process_partial_withdrawals(spec, state, 0) - - -@with_capella_and_later -@spec_state_test -def test_success_excess_balance_but_no_max_effective_balance(spec, state): - validator_index = len(state.validators) // 2 - set_validator_partially_withdrawable(spec, state, validator_index) - validator = state.validators[validator_index] - - # To be partially withdrawable, the validator needs both a maxed out effective balance and an excess balance - validator.effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1 - - assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) - - yield from run_process_partial_withdrawals(spec, state, 0) - - -@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 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, excess_balance=rng.randint(1, 1000000000)) - - # 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)) diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py new file mode 100644 index 0000000000..c037729982 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py @@ -0,0 +1,426 @@ +from random 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.random import randomize_state +from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.withdrawals import ( + set_validator_fully_withdrawable, + set_validator_partially_withdrawable, + set_eth1_withdrawal_credential_with_balance, +) + + +def run_process_withdrawals_into_queue(spec, state, num_expected_withdrawals=None): + run_epoch_processing_to(spec, state, 'process_withdrawals_into_queue') + + pre_next_withdrawal_index = state.next_withdrawal_index + pre_withdrawal_queue = state.withdrawal_queue.copy() + + fully_withdrawable_indices = [] + partially_withdrawable_indices = [] + for index, validator in enumerate(state.validators): + if spec.is_fully_withdrawable_validator(validator, state.balances[index], spec.get_current_epoch(state)): + fully_withdrawable_indices.append(index) + elif spec.is_partially_withdrawable_validator(validator, state.balances[index]): + partially_withdrawable_indices.append(index) + + to_be_withdrawn_indices = list( + set(fully_withdrawable_indices + partially_withdrawable_indices) + )[:spec.MAX_WITHDRAWALS_PER_EPOCH] + if num_expected_withdrawals is not None: + assert min(len(to_be_withdrawn_indices), spec.MAX_WITHDRAWALS_PER_EPOCH) == num_expected_withdrawals + else: + num_expected_withdrawals = len(to_be_withdrawn_indices) + + to_be_partially_withdrawn_indices = [ + index for index in partially_withdrawable_indices + if index in to_be_withdrawn_indices + ] + num_partial_withdrawals = len(to_be_partially_withdrawn_indices) + + yield 'pre', state + spec.process_withdrawals_into_queue(state) + yield 'post', state + + # check fully withdrawable indices + for index in fully_withdrawable_indices: + if index in to_be_withdrawn_indices: + assert state.balances[index] == 0 + + # check partially withdrawable indices + post_partially_withdrawable_indices = [ + index for index, validator in enumerate(state.validators) + if ( + not spec.is_fully_withdrawable_validator(validator, state.balances[index], spec.get_current_epoch(state)) + and spec.is_partially_withdrawable_validator(validator, state.balances[index]) + ) + ] + print('partially_withdrawable_indices', partially_withdrawable_indices) + 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 + + +# +# Fully +# + +@with_capella_and_later +@spec_state_test +def test_no_withdrawable_validators(spec, state): + pre_validators = state.validators.copy() + yield from run_process_withdrawals_into_queue(spec, state, 0) + + assert pre_validators == state.validators + + +@with_capella_and_later +@spec_state_test +def test_withdrawable_epoch_but_0_balance(spec, state): + current_epoch = spec.get_current_epoch(state) + set_validator_fully_withdrawable(spec, state, 0, current_epoch) + + state.validators[0].effective_balance = 10000000000 + state.balances[0] = 0 + + yield from run_process_withdrawals_into_queue(spec, state, 0) + + +@with_capella_and_later +@spec_state_test +def test_withdrawable_epoch_but_0_effective_balance_0_balance(spec, state): + current_epoch = spec.get_current_epoch(state) + set_validator_fully_withdrawable(spec, state, 0, current_epoch) + + state.validators[0].effective_balance = 0 + state.balances[0] = 0 + + yield from run_process_withdrawals_into_queue(spec, state, 0) + + +@with_capella_and_later +@spec_state_test +def test_withdrawable_epoch_but_0_effective_balance_nonzero_balance(spec, state): + current_epoch = spec.get_current_epoch(state) + set_validator_fully_withdrawable(spec, state, 0, current_epoch) + + state.validators[0].effective_balance = 0 + state.balances[0] = 100000000 + + yield from run_process_withdrawals_into_queue(spec, state, 1) + + +@with_capella_and_later +@spec_state_test +def test_no_withdrawals_but_some_next_epoch(spec, state): + current_epoch = spec.get_current_epoch(state) + + # Make a few validators withdrawable at the *next* epoch + for index in range(3): + set_validator_fully_withdrawable(spec, state, index, current_epoch + 1) + + yield from run_process_withdrawals_into_queue(spec, state, 0) + + +@with_capella_and_later +@spec_state_test +def test_single_withdrawal(spec, state): + # Make one validator withdrawable + set_validator_fully_withdrawable(spec, state, 0) + + assert state.next_withdrawal_index == 0 + yield from run_process_withdrawals_into_queue(spec, state, 1) + + assert state.next_withdrawal_index == 1 + + +@with_capella_and_later +@spec_state_test +def test_multi_withdrawal(spec, state): + # Make a few validators withdrawable + for index in range(3): + set_validator_fully_withdrawable(spec, state, index) + + yield from run_process_withdrawals_into_queue(spec, state, 3) + + +@with_capella_and_later +@spec_state_test +def test_all_withdrawal(spec, state): + # Make all validators withdrawable + for index in range(len(state.validators)): + set_validator_fully_withdrawable(spec, state, index) + + num_expected_withdrawals = min(spec.MAX_WITHDRAWALS_PER_EPOCH, len(state.validators)) + yield from run_process_withdrawals_into_queue(spec, state, num_expected_withdrawals) + + +def run_random_full_withdrawals_test(spec, state, rng): + randomize_state(spec, state, rng) + for index in range(len(state.validators)): + # 50% withdrawable + if rng.choice([True, False]): + set_validator_fully_withdrawable(spec, state, index) + validator = state.validators[index] + # 12.5% unset credentials + if rng.randint(0, 7) == 0: + validator.withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + # 12.5% not enough balance + if rng.randint(0, 7) == 0: + state.balances[index] = 0 + # 12.5% not close enough epoch + if rng.randint(0, 7) == 0: + validator.withdrawable_epoch += 1 + + yield from run_process_withdrawals_into_queue(spec, state, None) + + +@with_capella_and_later +@spec_state_test +def test_random_withdrawals_0(spec, state): + yield from run_random_full_withdrawals_test(spec, state, Random(444)) + + +@with_capella_and_later +@spec_state_test +def test_random_withdrawals_1(spec, state): + yield from run_random_full_withdrawals_test(spec, state, Random(420)) + + +@with_capella_and_later +@spec_state_test +def test_random_withdrawals_2(spec, state): + yield from run_random_full_withdrawals_test(spec, state, Random(200)) + + +@with_capella_and_later +@spec_state_test +def test_random_withdrawals_3(spec, state): + yield from run_random_full_withdrawals_test(spec, state, Random(2000000)) + +# +# Partial +# + + +@with_capella_and_later +@spec_state_test +def test_success_no_withdrawable(spec, state): + pre_validators = state.validators.copy() + yield from run_process_withdrawals_into_queue(spec, state, 0) + + assert pre_validators == state.validators + + +@with_capella_and_later +@spec_state_test +def test_success_no_max_effective_balance(spec, state): + validator_index = len(state.validators) // 2 + # To be partially withdrawable, the validator's effective balance must be maxed out + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE - 1) + validator = state.validators[validator_index] + + assert validator.effective_balance < spec.MAX_EFFECTIVE_BALANCE + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + yield from run_process_withdrawals_into_queue(spec, state, 0) + + +@with_capella_and_later +@spec_state_test +def test_success_no_excess_balance(spec, state): + validator_index = len(state.validators) // 2 + # To be partially withdrawable, the validator needs an excess balance + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, spec.MAX_EFFECTIVE_BALANCE) + validator = state.validators[validator_index] + + assert validator.effective_balance == spec.MAX_EFFECTIVE_BALANCE + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + yield from run_process_withdrawals_into_queue(spec, state, 0) + + +@with_capella_and_later +@spec_state_test +def test_success_excess_balance_but_no_max_effective_balance(spec, state): + validator_index = len(state.validators) // 2 + set_validator_partially_withdrawable(spec, state, validator_index) + validator = state.validators[validator_index] + + # To be partially withdrawable, the validator needs both a maxed out effective balance and an excess balance + validator.effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1 + + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + yield from run_process_withdrawals_into_queue(spec, state, 0) + + +@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_withdrawals_into_queue(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_withdrawals_into_queue(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_withdrawals_into_queue(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_withdrawals_into_queue(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_withdrawals_into_queue(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_withdrawals_into_queue(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_withdrawals_into_queue(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_WITHDRAWALS_PER_EPOCH + + for i in range(spec.MAX_WITHDRAWALS_PER_EPOCH): + set_validator_partially_withdrawable(spec, state, i) + + yield from run_process_withdrawals_into_queue(spec, state, spec.MAX_WITHDRAWALS_PER_EPOCH) + + +@with_capella_and_later +@with_presets([MINIMAL], reason="not 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_WITHDRAWALS_PER_EPOCH + 1 + + # More than MAX_WITHDRAWALS_PER_EPOCH partially withdrawable + for i in range(spec.MAX_WITHDRAWALS_PER_EPOCH + 1): + set_validator_partially_withdrawable(spec, state, i) + + # Should only have MAX_WITHDRAWALS_PER_EPOCH withdrawals created + yield from run_process_withdrawals_into_queue(spec, state, spec.MAX_WITHDRAWALS_PER_EPOCH) + + +def run_random_partial_withdrawals_test(spec, state, rng): + for _ in range(rng.randint(0, 2)): + next_epoch(spec, state) + # NOTE: couldn't run `randomize_state` here because we want to only test partial withdrawal + + num_validators = len(state.validators) + state.next_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, excess_balance=rng.randint(1, 1000000000)) + + # 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_withdrawals_into_queue(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(0)) + + +@with_capella_and_later +@spec_state_test +def test_random_1(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, 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(2)) + + +@with_capella_and_later +@spec_state_test +def test_random_3(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, 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(4)) + + +@with_capella_and_later +@spec_state_test +def test_random_5(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, Random(5)) diff --git a/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py index 28c20a2cd3..24efc23016 100644 --- a/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py @@ -85,7 +85,7 @@ def test_partial_withdrawal_in_epoch_transition(spec, state): @spec_state_test def test_many_partial_withdrawals_in_epoch_transition(spec, state): assert len(state.validators) > spec.MAX_WITHDRAWALS_PER_PAYLOAD - assert spec.MAX_PARTIAL_WITHDRAWALS_PER_EPOCH > spec.MAX_WITHDRAWALS_PER_PAYLOAD + assert spec.MAX_WITHDRAWALS_PER_EPOCH > spec.MAX_WITHDRAWALS_PER_PAYLOAD for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1): index = (i + state.next_withdrawal_index) % len(state.validators) diff --git a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py index e18c239f1e..1e8a82083d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py @@ -28,8 +28,7 @@ def get_process_calls(spec): 'process_participation_record_updates' ), 'process_sync_committee_updates', # altair - 'process_full_withdrawals', # capella - 'process_partial_withdrawals', # capella + 'process_withdrawals_into_queue', # capella # TODO: add sharding processing functions when spec stabilizes. ] From 8794d865de364d321f556ac702b37fe07ab8f1d7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 19 Oct 2022 10:20:59 -0500 Subject: [PATCH 3/3] Remove debug print --- .../epoch_processing/test_process_withdrawals_into_queue.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py index c037729982..a5606cfc41 100644 --- a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py +++ b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_withdrawals_into_queue.py @@ -63,7 +63,6 @@ def run_process_withdrawals_into_queue(spec, state, num_expected_withdrawals=Non and spec.is_partially_withdrawable_validator(validator, state.balances[index]) ) ] - print('partially_withdrawable_indices', partially_withdrawable_indices) 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