diff --git a/specs/electra/fork.md b/specs/electra/fork.md index 6ac5be5b0..c9c2812f6 100644 --- a/specs/electra/fork.md +++ b/specs/electra/fork.md @@ -74,10 +74,12 @@ def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: epoch = deneb.get_current_epoch(pre) latest_execution_payload_header = pre.latest_execution_payload_header - exit_epochs = [v.exit_epoch for v in pre.validators if v.exit_epoch != FAR_FUTURE_EPOCH] - if not exit_epochs: - exit_epochs = [get_current_epoch(pre)] - earliest_exit_epoch = max(exit_epochs) + 1 + earliest_exit_epoch = compute_activation_exit_epoch(get_current_epoch(pre)) + for validator in pre.validators: + if validator.exit_epoch != FAR_FUTURE_EPOCH: + if validator.exit_epoch > earliest_exit_epoch: + earliest_exit_epoch = validator.exit_epoch + earliest_exit_epoch += 1 post = BeaconState( # Versioning 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 884bfcb4e..a27ed777f 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 @@ -130,3 +130,61 @@ def test_fork_has_compounding_withdrawal_credential(spec, phases, state): post_state = yield from run_fork_test(post_spec, state) assert len(post_state.pending_deposits) > 0 + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_earliest_exit_epoch_no_validator_exits(spec, phases, state): + # advance state so the current epoch is not zero + next_epoch(spec, state) + next_epoch(spec, state) + next_epoch(spec, state) + + post_spec = phases[ELECTRA] + post_state = yield from run_fork_test(post_spec, state) + + # the earliest exit epoch should be the compute_activation_exit_epoch + 1 + current_epoch = post_spec.compute_epoch_at_slot(post_state.slot) + expected_earliest_exit_epoch = post_spec.compute_activation_exit_epoch(current_epoch) + 1 + assert post_state.earliest_exit_epoch == expected_earliest_exit_epoch + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_earliest_exit_epoch_is_max_validator_exit_epoch(spec, phases, state): + # assign some validators exit epochs + state.validators[0].exit_epoch = 20 + state.validators[1].exit_epoch = 30 + state.validators[2].exit_epoch = 10 + + post_state = yield from run_fork_test(phases[ELECTRA], state) + + # the earliest exit epoch should be the greatest validator exit epoch + 1 + expected_earliest_exit_epoch = post_state.validators[1].exit_epoch + 1 + assert post_state.earliest_exit_epoch == expected_earliest_exit_epoch + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_earliest_exit_epoch_less_than_current_epoch(spec, phases, state): + # assign a validator an exit epoch + state.validators[0].exit_epoch = 1 + + # advance state so the current epoch is not zero + next_epoch(spec, state) + next_epoch(spec, state) + next_epoch(spec, state) + + post_spec = phases[ELECTRA] + post_state = yield from run_fork_test(post_spec, state) + + # the earliest exit epoch should be the compute_activation_exit_epoch + 1 + current_epoch = post_spec.compute_epoch_at_slot(post_state.slot) + expected_earliest_exit_epoch = post_spec.compute_activation_exit_epoch(current_epoch) + 1 + assert post_state.earliest_exit_epoch == expected_earliest_exit_epoch