diff --git a/specs/electra/fork.md b/specs/electra/fork.md index 909556c666..b908c542ea 100644 --- a/specs/electra/fork.md +++ b/specs/electra/fork.md @@ -72,7 +72,6 @@ an irregular state change is made to upgrade to Electra. ```python def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: epoch = deneb.get_current_epoch(pre) - latest_execution_payload_header = pre.latest_execution_payload_header earliest_exit_epoch = compute_activation_exit_epoch(get_current_epoch(pre)) for validator in pre.validators: @@ -121,7 +120,7 @@ def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: current_sync_committee=pre.current_sync_committee, next_sync_committee=pre.next_sync_committee, # Execution-layer - latest_execution_payload_header=latest_execution_payload_header, # [Modified in Electra:EIP6110:EIP7002] + latest_execution_payload_header=pre.latest_execution_payload_header, # Withdrawals next_withdrawal_index=pre.next_withdrawal_index, next_withdrawal_validator_index=pre.next_withdrawal_validator_index, diff --git a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py index a27ed777fe..aade4a1605 100644 --- a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py +++ b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py @@ -87,11 +87,22 @@ def test_fork_random_large_validator_set(spec, phases, state): @with_state @with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) def test_fork_pre_activation(spec, phases, state): + index = 0 post_spec = phases[ELECTRA] - state.validators[0].activation_epoch = spec.FAR_FUTURE_EPOCH + state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH post_state = yield from run_fork_test(post_spec, state) - assert len(post_state.pending_deposits) > 0 + validator = post_state.validators[index] + assert post_state.balances[index] == 0 + assert validator.effective_balance == 0 + assert validator.activation_eligibility_epoch == spec.FAR_FUTURE_EPOCH + assert post_state.pending_deposits == [post_spec.PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=state.balances[index], + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, + )] @with_phases(phases=[DENEB], other_phases=[ELECTRA]) @@ -123,13 +134,21 @@ def test_fork_pending_deposits_are_sorted(spec, phases, state): @with_state @with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) def test_fork_has_compounding_withdrawal_credential(spec, phases, state): + index = 0 post_spec = phases[ELECTRA] - validator = state.validators[0] - state.balances[0] = post_spec.MIN_ACTIVATION_BALANCE + 1 + validator = state.validators[index] + state.balances[index] = post_spec.MIN_ACTIVATION_BALANCE + 1 validator.withdrawal_credentials = post_spec.COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] post_state = yield from run_fork_test(post_spec, state) - assert len(post_state.pending_deposits) > 0 + assert post_state.balances[index] == post_spec.MIN_ACTIVATION_BALANCE + assert post_state.pending_deposits == [post_spec.PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=state.balances[index] - post_spec.MIN_ACTIVATION_BALANCE, + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, + )] @with_phases(phases=[DENEB], other_phases=[ELECTRA]) diff --git a/tests/core/pyspec/eth2spec/test/electra/transition/__init__.py b/tests/core/pyspec/eth2spec/test/electra/transition/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/electra/transition/test_operations.py new file mode 100644 index 0000000000..7f5a1af9f5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/transition/test_operations.py @@ -0,0 +1,98 @@ +from eth2spec.test.context import ( + ForkMeta, + always_bls, + with_fork_metas, + with_presets, +) +from eth2spec.test.helpers.constants import ( + AFTER_ELECTRA_PRE_POST_FORKS, + MINIMAL, +) +from eth2spec.test.helpers.fork_transition import ( + OperationType, + run_transition_with_operation, +) + + +# +# DepositRequest +# + +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_ELECTRA_PRE_POST_FORKS]) +@always_bls +def test_transition_with_deposit_request_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a DEPOSIT_REQUEST right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.DEPOSIT_REQUEST, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +# +# WithdrawalRequest +# + +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=66) + for pre, post in AFTER_ELECTRA_PRE_POST_FORKS]) +@with_presets([MINIMAL], reason="too slow") +@always_bls +def test_transition_with_full_withdrawal_request_right_after_fork( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag +): + """ + Create a WITHDRAWAL_REQUEST right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.WITHDRAWAL_REQUEST, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +# +# ConsolidationRequest +# + +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_ELECTRA_PRE_POST_FORKS]) +@always_bls +def test_transition_with_consolidation_request_right_after_fork( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag +): + """ + Create a CONSOLIDATION_REQUEST right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.CONSOLIDATION_REQUEST, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/consolidations.py b/tests/core/pyspec/eth2spec/test/helpers/consolidations.py new file mode 100644 index 0000000000..c16108b97a --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/consolidations.py @@ -0,0 +1,15 @@ +from eth2spec.test.helpers.withdrawals import ( + set_eth1_withdrawal_credential_with_balance +) + + +def prepare_switch_to_compounding_request(spec, state, validator_index, address=None): + validator = state.validators[validator_index] + if not spec.has_execution_withdrawal_credential(validator): + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + + return spec.ConsolidationRequest( + source_address=state.validators[validator_index].withdrawal_credentials[12:], + source_pubkey=state.validators[validator_index].pubkey, + target_pubkey=state.validators[validator_index].pubkey, + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index 97ed1134e2..284dc32f03 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -74,7 +74,10 @@ ALL_PRE_POST_FORKS = POST_FORK_OF.items() DENEB_TRANSITION_UPGRADES_AND_AFTER = {key: value for key, value in POST_FORK_OF.items() if key not in [PHASE0, ALTAIR, BELLATRIX]} +ELECTRA_TRANSITION_UPGRADES_AND_AFTER = {key: value for key, value in POST_FORK_OF.items() + if key not in [PHASE0, ALTAIR, BELLATRIX, CAPELLA]} AFTER_DENEB_PRE_POST_FORKS = DENEB_TRANSITION_UPGRADES_AND_AFTER.items() +AFTER_ELECTRA_PRE_POST_FORKS = ELECTRA_TRANSITION_UPGRADES_AND_AFTER.items() # # Config and Preset diff --git a/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py index 886fc7cce0..2aeaaa995d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py @@ -54,7 +54,7 @@ def run_fork_test(post_spec, pre_state): stable_validator_fields = [ 'pubkey', 'withdrawal_credentials', 'slashed', - 'exit_epoch', 'withdrawable_epoch', + 'activation_epoch', 'exit_epoch', 'withdrawable_epoch', ] for field in stable_validator_fields: assert getattr(pre_validator, field) == getattr(post_validator, field) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 8eb72fdad5..69e1be669b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -21,6 +21,7 @@ ) from eth2spec.test.helpers.deposits import ( prepare_state_and_deposit, + prepare_deposit_request, ) from eth2spec.test.helpers.proposer_slashings import ( get_valid_proposer_slashing, @@ -37,6 +38,12 @@ from eth2spec.test.helpers.voluntary_exits import ( prepare_signed_exits, ) +from eth2spec.test.helpers.withdrawals import ( + prepare_withdrawal_request, +) +from eth2spec.test.helpers.consolidations import ( + prepare_switch_to_compounding_request, +) class OperationType(Enum): @@ -45,11 +52,18 @@ class OperationType(Enum): DEPOSIT = auto() VOLUNTARY_EXIT = auto() BLS_TO_EXECUTION_CHANGE = auto() + DEPOSIT_REQUEST = auto() + WITHDRAWAL_REQUEST = auto() + CONSOLIDATION_REQUEST = auto() def _set_operations_by_dict(block, operation_dict): for key, value in operation_dict.items(): - setattr(block.body, key, value) + # to handle e.g. `execution_requests.deposits` and `deposits` + obj = block.body + for attr in key.split('.')[:-1]: + obj = getattr(obj, attr) + setattr(obj, key.split('.')[-1], value) def _state_transition_and_sign_block_at_slot(spec, @@ -328,6 +342,21 @@ def run_transition_with_operation(state, selected_validator_index = 0 bls_to_execution_changes = [get_signed_address_change(spec, state, selected_validator_index)] operation_dict = {'bls_to_execution_changes': bls_to_execution_changes} + elif operation_type == OperationType.DEPOSIT_REQUEST: + # create a new deposit request + selected_validator_index = len(state.validators) + amount = post_spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(post_spec, selected_validator_index, amount, signed=True) + operation_dict = {'execution_requests.deposits': [deposit_request]} + elif operation_type == OperationType.WITHDRAWAL_REQUEST: + selected_validator_index = 0 + withdrawal_request = prepare_withdrawal_request( + post_spec, state, selected_validator_index, amount=post_spec.FULL_EXIT_REQUEST_AMOUNT) + operation_dict = {'execution_requests.withdrawals': [withdrawal_request]} + elif operation_type == OperationType.CONSOLIDATION_REQUEST: + selected_validator_index = 0 + consolidation_request = prepare_switch_to_compounding_request(post_spec, state, selected_validator_index) + operation_dict = {'execution_requests.consolidations': [consolidation_request]} def _check_state(): if operation_type == OperationType.PROPOSER_SLASHING: @@ -352,6 +381,20 @@ def _check_state(): elif operation_type == OperationType.BLS_TO_EXECUTION_CHANGE: validator = state.validators[selected_validator_index] assert validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + elif operation_type == OperationType.DEPOSIT_REQUEST: + assert state.pending_deposits == [post_spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=state.slot, + )] + elif operation_type == OperationType.WITHDRAWAL_REQUEST: + validator = state.validators[selected_validator_index] + assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH + elif operation_type == OperationType.CONSOLIDATION_REQUEST: + validator = state.validators[selected_validator_index] + assert validator.withdrawal_credentials[:1] == post_spec.COMPOUNDING_WITHDRAWAL_PREFIX yield "pre", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py index b1cfaf869d..71f8b5ebb8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py @@ -128,6 +128,22 @@ def prepare_pending_withdrawal(spec, state, validator_index, return withdrawal + +def prepare_withdrawal_request(spec, state, validator_index, address=None, amount=None): + validator = state.validators[validator_index] + if not spec.has_execution_withdrawal_credential(validator): + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + + if amount is None: + amount = spec.FULL_EXIT_REQUEST_AMOUNT + + return spec.WithdrawalRequest( + source_address=state.validators[validator_index].withdrawal_credentials[12:], + validator_pubkey=state.validators[validator_index].pubkey, + amount=amount, + ) + + # # Run processing # diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index 75ce72b28c..7acef3335f 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -20,6 +20,9 @@ test_operations as test_deneb_operations, test_transition as test_deneb_transition, ) +from eth2spec.test.electra.transition import ( + test_operations as test_electra_operations, +) def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_name: str) -> gen_typing.TestProvider: @@ -49,6 +52,7 @@ def cases_fn() -> Iterable[gen_typing.TestCase]: test_altair_operations, test_deneb_operations, test_deneb_transition, + test_electra_operations, ) for transition_test_module in all_tests: for pre_fork, post_fork in ALL_PRE_POST_FORKS: