From 7a0bf5cea2f4d06a0090325df3f0066df97fc17c Mon Sep 17 00:00:00 2001 From: fradamt Date: Thu, 11 Apr 2024 13:52:02 +0200 Subject: [PATCH] fix/add tests for process_execution_layer_withdraw_request --- ...rocess_execution_layer_withdraw_request.py | 203 ++++++++++++++++-- 1 file changed, 189 insertions(+), 14 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/eip7251/block_processing/test_process_execution_layer_withdraw_request.py b/tests/core/pyspec/eth2spec/test/eip7251/block_processing/test_process_execution_layer_withdraw_request.py index a837f329f..f1233915e 100644 --- a/tests/core/pyspec/eth2spec/test/eip7251/block_processing/test_process_execution_layer_withdraw_request.py +++ b/tests/core/pyspec/eth2spec/test/eip7251/block_processing/test_process_execution_layer_withdraw_request.py @@ -10,6 +10,7 @@ from eth2spec.test.helpers.state import ( ) from eth2spec.test.helpers.withdrawals import ( set_eth1_withdrawal_credential_with_balance, + set_compounding_withdrawal_credential, ) @@ -33,11 +34,38 @@ def test_basic_exit(spec, state): execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( source_address=address, validator_pubkey=validator_pubkey, - amount=0, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, ) yield from run_execution_layer_withdraw_request_processing(spec, state, execution_layer_withdraw_request) +@with_eip7251_and_later +@spec_state_test +@with_presets([MINIMAL], "need full partial withdrawal queue") +def test_basic_exit_with_full_partial_withdrawal_queue(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + # Fill the partial withdrawal queue to the max (with a different validator index) + partial_withdrawal = spec.PendingPartialWithdrawal(index=1, amount=1, withdrawable_epoch=current_epoch) + state.pending_partial_withdrawals = [partial_withdrawal] * spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT + + # Exit should still be processed + yield from run_execution_layer_withdraw_request_processing( + spec, state, execution_layer_withdraw_request, + ) + + +# Invalid tests @with_eip7251_and_later @spec_state_test @@ -54,7 +82,7 @@ def test_incorrect_source_address(spec, state): execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( source_address=incorrect_address, validator_pubkey=validator_pubkey, - amount=0, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, ) yield from run_execution_layer_withdraw_request_processing( @@ -81,7 +109,7 @@ def test_incorrect_withdrawal_credential_prefix(spec, state): execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( source_address=address, validator_pubkey=validator_pubkey, - amount=0, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, ) yield from run_execution_layer_withdraw_request_processing( @@ -105,7 +133,7 @@ def test_on_exit_initiated_validator(spec, state): execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( source_address=address, validator_pubkey=validator_pubkey, - amount=0, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, ) yield from run_execution_layer_withdraw_request_processing( @@ -124,7 +152,7 @@ def test_activation_epoch_less_than_shard_committee_period(spec, state): execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( source_address=address, validator_pubkey=validator_pubkey, - amount=0, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, ) assert spec.get_current_epoch(state) < ( @@ -136,31 +164,163 @@ def test_activation_epoch_less_than_shard_committee_period(spec, state): ) + # Partial withdrawals tests @with_eip7251_and_later @spec_state_test @with_presets([MINIMAL]) +def test_basic_partial_withdrawal_request(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Ensure that the validator has sufficient excess balance + state.balances[validator_index] += 2*amount + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_execution_layer_withdraw_request_processing( + spec, state, execution_layer_withdraw_request, + ) + + # Check that the assigned exit epoch is correct + assert state.earliest_exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + + +# Invalid partial withdrawal tests + +@with_eip7251_and_later +@spec_state_test +@with_presets([MINIMAL], "need full partial withdrawal queue") def test_partial_withdrawal_queue_full(spec, state): state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH current_epoch = spec.get_current_epoch(state) validator_index = spec.get_active_validator_indices(state, current_epoch)[0] validator_pubkey = state.validators[validator_index].pubkey address = b'\x22' * 20 - set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Ensure that the validator has sufficient excess balance + state.balances[validator_index] += 2*amount + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( source_address=address, validator_pubkey=validator_pubkey, - amount=10 ** 9, + amount=amount, ) - partial_withdrawal = spec.PendingPartialWithdrawal(index=0, amount=1, withdrawable_epoch=current_epoch) + # Fill the partial withdrawal queue to the max + partial_withdrawal = spec.PendingPartialWithdrawal(index=1, amount=1, withdrawable_epoch=current_epoch) state.pending_partial_withdrawals = [partial_withdrawal] * spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT yield from run_execution_layer_withdraw_request_processing( spec, state, execution_layer_withdraw_request, success=False ) +@with_eip7251_and_later +@spec_state_test +def test_invalid_no_compounding_credentials(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Ensure that the validator has sufficient excess balance + state.balances[validator_index] += 2*amount + + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_execution_layer_withdraw_request_processing( + spec, state, execution_layer_withdraw_request, success=False, + ) + + +@with_eip7251_and_later +@spec_state_test +def test_invalid_no_excess_balance(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_execution_layer_withdraw_request_processing( + spec, state, execution_layer_withdraw_request, success=False + ) + +@with_eip7251_and_later +@spec_state_test +def test_invalid_pending_withdrawals_consume_all_excess_balance(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Add excess balance + state.balances[validator_index] += 10 * amount + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + # Add pending withdrawals totalling an amount equal to the excess balance + partial_withdrawal = spec.PendingPartialWithdrawal(index=validator_index, + amount=amount, + withdrawable_epoch=current_epoch) + state.pending_partial_withdrawals = [partial_withdrawal] * 10 + + yield from run_execution_layer_withdraw_request_processing( + spec, state, execution_layer_withdraw_request, success=False + ) + +@with_eip7251_and_later +@spec_state_test +def test_invalid_insufficient_effective_balance(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b'\x22' * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Make effective balance insufficient + state.validators[validator_index].effective_balance -= spec.EFFECTIVE_BALANCE_INCREMENT + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + execution_layer_withdraw_request = spec.ExecutionLayerWithdrawRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_execution_layer_withdraw_request_processing( + spec, state, execution_layer_withdraw_request, success=False, + ) + # # Run processing # @@ -188,26 +348,41 @@ def run_execution_layer_withdraw_request_processing( return pre_exit_epoch = state.validators[validator_index].exit_epoch - pre_pending_partial_withdrawals = state.pending_partial_withdrawals + pre_pending_partial_withdrawals = state.pending_partial_withdrawals.copy() pre_balance = state.balances[validator_index] spec.process_execution_layer_withdraw_request(state, execution_layer_withdraw_request) yield 'post', state - if execution_layer_withdraw_request.amount == 0: + # Full exit request + if execution_layer_withdraw_request.amount == spec.FULL_EXIT_REQUEST_AMOUNT: if success: assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.get_pending_balance_to_withdraw(state, validator_index) == 0 else: assert state.validators[validator_index].exit_epoch == pre_exit_epoch + assert state.pending_partial_withdrawals == pre_pending_partial_withdrawals + # Partial withdrawal request else: if success: assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH assert state.balances[validator_index] == pre_balance - post_length = len(state.pending_partial_withdrawals) - assert post_length == len(pre_pending_partial_withdrawals) + 1 - assert post_length < spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT - assert state.pending_partial_withdrawals[post_length - 1].validator_index == validator_index + expected_amount = compute_amount_to_withdraw(spec, state, validator_index, execution_layer_withdraw_request.amount) + expected_withdrawable_epoch = state.earliest_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + expected_partial_withdrawal = spec.PendingPartialWithdrawal(index=validator_index, + amount=expected_amount, + withdrawable_epoch=expected_withdrawable_epoch) + assert state.pending_partial_withdrawals == pre_pending_partial_withdrawals + [expected_partial_withdrawal] + else: assert state.pending_partial_withdrawals == pre_pending_partial_withdrawals + + +def compute_amount_to_withdraw(spec, state, index, amount): + pending_balance_to_withdraw = spec.get_pending_balance_to_withdraw(state, index) + return min( + state.balances[index] - spec.MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw, + amount + ) \ No newline at end of file