diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index 29f1f807fe..3734b1cf61 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -1626,6 +1626,12 @@ def process_consolidation_request( return if target_validator.exit_epoch != FAR_FUTURE_EPOCH: return + # Verify the source has been active long enough + if current_epoch < source_validator.activation_epoch + SHARD_COMMITTEE_PERIOD: + return + # Verify the source has no pending withdrawals in the queue + if get_pending_balance_to_withdraw(state, source_index) > 0: + return # Initiate source validator exit and append pending consolidation source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn( diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py index 49744946f0..8fdbb8e2e5 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py @@ -28,6 +28,8 @@ @spec_test @single_phase def test_basic_consolidation_in_current_consolidation_epoch(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -75,6 +77,8 @@ def test_basic_consolidation_in_current_consolidation_epoch(spec, state): @spec_test @single_phase def test_basic_consolidation_with_excess_target_balance(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -125,6 +129,8 @@ def test_basic_consolidation_with_excess_target_balance(spec, state): @spec_test @single_phase def test_basic_consolidation_with_excess_target_balance_and_compounding_credentials(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -175,6 +181,8 @@ def test_basic_consolidation_with_excess_target_balance_and_compounding_credenti @spec_test @single_phase def test_basic_consolidation_in_new_consolidation_epoch(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn # Set consolidation balance to consume to some arbitrary nonzero value below the churn limit state.consolidation_balance_to_consume = spec.EFFECTIVE_BALANCE_INCREMENT @@ -220,6 +228,8 @@ def test_basic_consolidation_in_new_consolidation_epoch(spec, state): @spec_test @single_phase def test_basic_consolidation_with_preexisting_churn(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -267,6 +277,8 @@ def test_basic_consolidation_with_preexisting_churn(spec, state): @spec_test @single_phase def test_basic_consolidation_with_insufficient_preexisting_churn(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -318,6 +330,8 @@ def test_basic_consolidation_with_insufficient_preexisting_churn(spec, state): @spec_test @single_phase def test_basic_consolidation_with_compounding_credentials(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -363,6 +377,8 @@ def test_basic_consolidation_with_compounding_credentials(spec, state): @spec_test @single_phase def test_consolidation_churn_limit_balance(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -411,6 +427,8 @@ def test_consolidation_churn_limit_balance(spec, state): @spec_test @single_phase def test_consolidation_balance_larger_than_churn_limit(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -458,6 +476,8 @@ def test_consolidation_balance_larger_than_churn_limit(spec, state): @spec_test @single_phase def test_consolidation_balance_through_two_churn_epochs(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -498,6 +518,8 @@ def test_consolidation_balance_through_two_churn_epochs(spec, state): @with_electra_and_later @spec_state_test def test_basic_switch_to_compounding(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -583,6 +605,9 @@ def test_switch_to_compounding_with_pending_consolidations_at_limit(spec, state) @spec_test @single_phase def test_incorrect_exceed_pending_consolidations_limit(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + state.pending_consolidations = [ spec.PendingConsolidation(source_index=0, target_index=1) ] * spec.PENDING_CONSOLIDATIONS_LIMIT @@ -614,6 +639,9 @@ def test_incorrect_exceed_pending_consolidations_limit(spec, state): @spec_state_test @single_phase def test_incorrect_not_enough_consolidation_churn_available(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + state.pending_consolidations = [ spec.PendingConsolidation(source_index=0, target_index=1) ] @@ -651,6 +679,8 @@ def test_incorrect_not_enough_consolidation_churn_available(spec, state): @spec_test @single_phase def test_incorrect_exited_source(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up an otherwise correct consolidation current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -686,6 +716,8 @@ def test_incorrect_exited_source(spec, state): @spec_test @single_phase def test_incorrect_exited_target(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up an otherwise correct consolidation current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -720,6 +752,8 @@ def test_incorrect_exited_target(spec, state): @spec_test @single_phase def test_incorrect_inactive_source(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up an otherwise correct consolidation current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -755,6 +789,8 @@ def test_incorrect_inactive_source(spec, state): @spec_test @single_phase def test_incorrect_inactive_target(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up an otherwise correct consolidation current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -790,6 +826,8 @@ def test_incorrect_inactive_target(spec, state): @spec_test @single_phase def test_incorrect_no_source_execution_withdrawal_credential(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up a correct consolidation, but source does not have # an execution withdrawal credential current_epoch = spec.get_current_epoch(state) @@ -820,6 +858,8 @@ def test_incorrect_no_source_execution_withdrawal_credential(spec, state): @spec_test @single_phase def test_incorrect_no_target_execution_withdrawal_credential(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up a correct consolidation, but target does not have # an execution withdrawal credential current_epoch = spec.get_current_epoch(state) @@ -852,6 +892,8 @@ def test_incorrect_no_target_execution_withdrawal_credential(spec, state): @spec_test @single_phase def test_incorrect_incorrect_source_address(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up an otherwise correct consolidation current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -885,6 +927,8 @@ def test_incorrect_incorrect_source_address(spec, state): @spec_test @single_phase def test_incorrect_unknown_source_pubkey(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up an otherwise correct consolidation current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -918,6 +962,8 @@ def test_incorrect_unknown_source_pubkey(spec, state): @spec_test @single_phase def test_incorrect_unknown_target_pubkey(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up an otherwise correct consolidation current_epoch = spec.get_current_epoch(state) source_index = spec.get_active_validator_indices(state, current_epoch)[0] @@ -942,6 +988,80 @@ def test_incorrect_unknown_target_pubkey(spec, state): ) +@with_electra_and_later +@with_presets([MINIMAL], "need sufficient consolidation churn limit") +@with_custom_state( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_source_has_pending_withdrawal(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + # Set up an otherwise correct consolidation + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + source_address = b"\x22" * 20 + excess_balance = spec.EFFECTIVE_BALANCE_INCREMENT // 4 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address, balance=spec.MIN_ACTIVATION_BALANCE + excess_balance + ) + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Create pending withdrawal + pending_withdrawal = spec.PendingPartialWithdrawal( + index=0, amount=excess_balance, withdrawable_epoch=current_epoch + ) + state.pending_partial_withdrawals.append(pending_withdrawal) + + # Check the return condition + assert spec.get_pending_balance_to_withdraw(state, source_index) > 0 + + yield from run_consolidation_processing( + spec, state, consolidation, success=False + ) + + +@with_electra_and_later +@with_presets([MINIMAL], "need sufficient consolidation churn limit") +@with_custom_state( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_source_not_active_long_enough(spec, state): + # Set up an otherwise correct consolidation + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + source_address = b"\x22" * 20 + excess_balance = spec.EFFECTIVE_BALANCE_INCREMENT // 4 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address, balance=spec.MIN_ACTIVATION_BALANCE + excess_balance + ) + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Check the return condition + assert current_epoch < state.validators[source_index].activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD + + yield from run_consolidation_processing( + spec, state, consolidation, success=False + ) + + @with_electra_and_later @spec_state_test def test_switch_to_compounding_exited_source(spec, state):