diff --git a/presets/mainnet/electra.yaml b/presets/mainnet/electra.yaml index 72c626ded2..8aa663cec8 100644 --- a/presets/mainnet/electra.yaml +++ b/presets/mainnet/electra.yaml @@ -30,7 +30,7 @@ MAX_ATTESTER_SLASHINGS_ELECTRA: 1 # `uint64(2**3)` (= 8) MAX_ATTESTATIONS_ELECTRA: 8 # `uint64(2**0)` (= 1) -MAX_CONSOLIDATIONS: 1 +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1 # Execution # --------------------------------------------------------------- diff --git a/presets/minimal/electra.yaml b/presets/minimal/electra.yaml index 11aa5e1f50..a5897f340a 100644 --- a/presets/minimal/electra.yaml +++ b/presets/minimal/electra.yaml @@ -30,7 +30,7 @@ MAX_ATTESTER_SLASHINGS_ELECTRA: 1 # `uint64(2**3)` (= 8) MAX_ATTESTATIONS_ELECTRA: 8 # `uint64(2**0)` (= 1) -MAX_CONSOLIDATIONS: 1 +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1 # Execution # --------------------------------------------------------------- diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index 62da891146..3220fea1bf 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -28,8 +28,7 @@ - [`PendingBalanceDeposit`](#pendingbalancedeposit) - [`PendingPartialWithdrawal`](#pendingpartialwithdrawal) - [`ExecutionLayerWithdrawalRequest`](#executionlayerwithdrawalrequest) - - [`Consolidation`](#consolidation) - - [`SignedConsolidation`](#signedconsolidation) + - [`ExecutionLayerConsolidationRequest`](#executionlayerconsolidationrequest) - [`PendingConsolidation`](#pendingconsolidation) - [Modified Containers](#modified-containers) - [`AttesterSlashing`](#attesterslashing) @@ -94,8 +93,8 @@ - [New `process_execution_layer_withdrawal_request`](#new-process_execution_layer_withdrawal_request) - [Deposit receipts](#deposit-receipts) - [New `process_deposit_receipt`](#new-process_deposit_receipt) - - [Consolidations](#consolidations) - - [New `process_consolidation`](#new-process_consolidation) + - [Execution layer consolidation requests](#execution-layer-consolidation-requests) + - [New `process_execution_layer_consolidation_request`](#new-process_execution_layer_consolidation_request) - [Testing](#testing) @@ -164,16 +163,16 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | | - | - | -| `MAX_CONSOLIDATIONS` | `uint64(1)` | +| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | *[New in Electra:EIP7549]* | +| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | *[New in Electra:EIP7549]* | ### Execution | Name | Value | Description | | - | - | - | | `MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of deposit receipts allowed in each payload | -| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | *[New in Electra:EIP7549]* | -| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | *[New in Electra:EIP7549]* | | `MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` | `uint64(2**4)` (= 16)| *[New in Electra:EIP7002]* Maximum number of execution layer withdrawal requests in each payload | +| `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` | `uint64(1)` (= 1) | *[New in Electra:EIP7002]* Maximum number of execution layer consolidation requests in each payload | ### Withdrawals processing @@ -238,25 +237,15 @@ class ExecutionLayerWithdrawalRequest(Container): amount: Gwei ``` -#### `Consolidation` - -*Note*: The container is new in EIP7251. - -```python -class Consolidation(Container): - source_index: ValidatorIndex - target_index: ValidatorIndex - epoch: Epoch -``` - -#### `SignedConsolidation` +#### `ExecutionLayerConsolidationRequest` *Note*: The container is new in EIP7251. ```python -class SignedConsolidation(Container): - message: Consolidation - signature: BLSSignature +class ExecutionLayerConsolidationRequest(Container): + source_address: ExecutionAddress + source_pubkey: BLSPubkey + target_pubkey: BLSPubkey ``` #### `PendingConsolidation` @@ -319,7 +308,6 @@ class BeaconBlockBody(Container): execution_payload: ExecutionPayload # [Modified in Electra:EIP6110:EIP7002] bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] - consolidations: List[SignedConsolidation, MAX_CONSOLIDATIONS] # [New in Electra:EIP7251] ``` #### `ExecutionPayload` @@ -348,6 +336,8 @@ class ExecutionPayload(Container): deposit_receipts: List[DepositReceipt, MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD] # [New in Electra:EIP6110] # [New in Electra:EIP7002:EIP7251] withdrawal_requests: List[ExecutionLayerWithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] + # [New in Electra:EIP7251] + consolidation_requests: List[ExecutionLayerConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] ``` #### `ExecutionPayloadHeader` @@ -375,6 +365,7 @@ class ExecutionPayloadHeader(Container): excess_blob_gas: uint64 deposit_receipts_root: Root # [New in Electra:EIP6110] withdrawal_requests_root: Root # [New in Electra:EIP7002:EIP7251] + consolidation_requests_root: Root # [New in Electra:EIP7251] ``` #### `BeaconState` @@ -1013,6 +1004,7 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi excess_blob_gas=payload.excess_blob_gas, deposit_receipts_root=hash_tree_root(payload.deposit_receipts), # [New in Electra:EIP6110] withdrawal_requests_root=hash_tree_root(payload.withdrawal_requests), # [New in Electra:EIP7002:EIP7251] + consolidation_requests_root=hash_tree_root(payload.consolidation_requests), # [New in Electra:EIP7251] ) ``` @@ -1042,10 +1034,11 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: for_ops(body.deposits, process_deposit) # [Modified in Electra:EIP7251] for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251] for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) + for_ops(body.execution_payload.deposit_receipts, process_deposit_receipt) # [New in Electra:EIP6110] # [New in Electra:EIP7002:EIP7251] for_ops(body.execution_payload.withdrawal_requests, process_execution_layer_withdrawal_request) - for_ops(body.execution_payload.deposit_receipts, process_deposit_receipt) # [New in Electra:EIP6110] - for_ops(body.consolidations, process_consolidation) # [New in Electra:EIP7251] + # [New in Electra:EIP7251] + for_ops(body.execution_payload.consolidation_requests, process_execution_layer_consolidation_request) ``` ##### Attestations @@ -1292,43 +1285,62 @@ def process_deposit_receipt(state: BeaconState, deposit_receipt: DepositReceipt) ) ``` -##### Consolidations +##### Execution layer consolidation requests -###### New `process_consolidation` +###### New `process_execution_layer_consolidation_request` ```python -def process_consolidation(state: BeaconState, signed_consolidation: SignedConsolidation) -> None: - # If the pending consolidations queue is full, no consolidations are allowed in the block - assert len(state.pending_consolidations) < PENDING_CONSOLIDATIONS_LIMIT - # If there is too little available consolidation churn limit, no consolidations are allowed in the block - assert get_consolidation_churn_limit(state) > MIN_ACTIVATION_BALANCE - consolidation = signed_consolidation.message +def process_execution_layer_consolidation_request( + state: BeaconState, + execution_layer_consolidation_request: ExecutionLayerConsolidationRequest +) -> None: + # If the pending consolidations queue is full, consolidation requests are ignored + if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT: + return + # If there is too little available consolidation churn limit, consolidation requests are ignored + if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE: + return + + validator_pubkeys = [v.pubkey for v in state.validators] + # Verify pubkeys exists + request_source_pubkey = execution_layer_consolidation_request.source_pubkey + request_target_pubkey = execution_layer_consolidation_request.target_pubkey + if request_source_pubkey not in validator_pubkeys: + return + if request_target_pubkey not in validator_pubkeys: + return + source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey)) + target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey)) + source_validator = state.validators[source_index] + target_validator = state.validators[target_index] + # Verify that source != target, so a consolidation cannot be used as an exit. - assert consolidation.source_index != consolidation.target_index + if source_index == target_index: + return + + # Verify source withdrawal credentials + has_correct_credential = has_execution_withdrawal_credential(source_validator) + is_correct_source_address = ( + source_validator.withdrawal_credentials[12:] == execution_layer_consolidation_request.source_address + ) + if not (has_correct_credential and is_correct_source_address): + return + + # Verify that target has execution withdrawal credentials + if not has_execution_withdrawal_credential(target_validator): + return - source_validator = state.validators[consolidation.source_index] - target_validator = state.validators[consolidation.target_index] # Verify the source and the target are active current_epoch = get_current_epoch(state) - assert is_active_validator(source_validator, current_epoch) - assert is_active_validator(target_validator, current_epoch) + if not is_active_validator(source_validator, current_epoch): + return + if not is_active_validator(target_validator, current_epoch): + return # Verify exits for source and target have not been initiated - assert source_validator.exit_epoch == FAR_FUTURE_EPOCH - assert target_validator.exit_epoch == FAR_FUTURE_EPOCH - # Consolidations must specify an epoch when they become valid; they are not valid before then - assert current_epoch >= consolidation.epoch - - # Verify the source and the target have Execution layer withdrawal credentials - assert has_execution_withdrawal_credential(source_validator) - assert has_execution_withdrawal_credential(target_validator) - # Verify the same withdrawal address - assert source_validator.withdrawal_credentials[12:] == target_validator.withdrawal_credentials[12:] - - # Verify consolidation is signed by the source and the target - domain = compute_domain(DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root) - signing_root = compute_signing_root(consolidation, domain) - pubkeys = [source_validator.pubkey, target_validator.pubkey] - assert bls.FastAggregateVerify(pubkeys, signing_root, signed_consolidation.signature) + if source_validator.exit_epoch != FAR_FUTURE_EPOCH: + return + if target_validator.exit_epoch != FAR_FUTURE_EPOCH: + return # Initiate source validator exit and append pending consolidation source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn( @@ -1338,8 +1350,8 @@ def process_consolidation(state: BeaconState, signed_consolidation: SignedConsol source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY ) state.pending_consolidations.append(PendingConsolidation( - source_index=consolidation.source_index, - target_index=consolidation.target_index + source_index=source_index, + target_index=target_index )) ``` diff --git a/specs/electra/fork.md b/specs/electra/fork.md index ffd5f21571..0660aaf7f7 100644 --- a/specs/electra/fork.md +++ b/specs/electra/fork.md @@ -91,7 +91,8 @@ def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: blob_gas_used=pre.latest_execution_payload_header.blob_gas_used, excess_blob_gas=pre.latest_execution_payload_header.excess_blob_gas, deposit_receipts_root=Root(), # [New in Electra:EIP6110] - withdrawal_requests_root=Root(), # [New in Electra:EIP7002], + withdrawal_requests_root=Root(), # [New in Electra:EIP7002] + consolidation_requests_root=Root(), # [New in Electra:EIP7251] ) exit_epochs = [v.exit_epoch for v in pre.validators if v.exit_epoch != FAR_FUTURE_EPOCH] diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation.py deleted file mode 100644 index a1267cb716..0000000000 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation.py +++ /dev/null @@ -1,811 +0,0 @@ -from eth2spec.test.helpers.constants import MINIMAL -from eth2spec.test.context import ( - with_electra_and_later, - with_presets, - always_bls, - spec_test, - single_phase, - with_custom_state, - scaled_churn_balances_exceed_activation_exit_churn_limit, - default_activation_threshold, -) -from eth2spec.test.helpers.keys import pubkey_to_privkey -from eth2spec.test.helpers.consolidations import ( - run_consolidation_processing, - sign_consolidation, -) -from eth2spec.test.helpers.withdrawals import ( - set_eth1_withdrawal_credential_with_balance, - set_compounding_withdrawal_credential, -) - -# *********************** -# * CONSOLIDATION TESTS * -# *********************** - - -@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_basic_consolidation_in_current_consolidation_epoch(spec, state): - # 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] - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, source_index) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - - # Set earliest consolidation epoch to the expected exit epoch - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - state.earliest_consolidation_epoch = expected_exit_epoch - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - # Set the consolidation balance to consume equal to churn limit - state.consolidation_balance_to_consume = consolidation_churn_limit - - yield from run_consolidation_processing(spec, state, signed_consolidation) - - # Check consolidation churn is decremented correctly - assert ( - state.consolidation_balance_to_consume - == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE - ) - # Check exit epoch - assert state.validators[0].exit_epoch == expected_exit_epoch - - -@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_basic_consolidation_in_new_consolidation_epoch(spec, state): - # 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 - 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_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, source_index) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing(spec, state, signed_consolidation) - - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - # Check consolidation churn is decremented correctly - # consolidation_balance_to_consume is replenished to the churn limit since we move to a new consolidation epoch - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - assert ( - state.consolidation_balance_to_consume - == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE - ) - # Check exit epochs - assert state.validators[0].exit_epoch == expected_exit_epoch - - -@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_basic_consolidation_with_preexisting_churn(spec, state): - # 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] - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, source_index) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - - # Set earliest consolidation epoch to the expected exit epoch - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - state.earliest_consolidation_epoch = expected_exit_epoch - # Set some nonzero preexisting churn lower than churn limit and sufficient to process the consolidation - preexisting_churn = 2 * spec.MIN_ACTIVATION_BALANCE - state.consolidation_balance_to_consume = preexisting_churn - - yield from run_consolidation_processing(spec, state, signed_consolidation) - - # Check consolidation churn is decremented correctly - assert ( - state.consolidation_balance_to_consume - == preexisting_churn - spec.MIN_ACTIVATION_BALANCE - ) - # Check exit epoch - assert state.validators[0].exit_epoch == expected_exit_epoch - - -@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_basic_consolidation_with_insufficient_preexisting_churn(spec, state): - # 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] - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, source_index) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - - # Set earliest consolidation epoch to the first available epoch - state.earliest_consolidation_epoch = spec.compute_activation_exit_epoch( - current_epoch - ) - # Set preexisting churn lower than required to process the consolidation - preexisting_churn = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT - state.consolidation_balance_to_consume = preexisting_churn - - yield from run_consolidation_processing(spec, state, signed_consolidation) - - # It takes one more epoch to process the consolidation due to insufficient churn - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1 - # Check consolidation churn is decremented correctly - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - remainder = spec.MIN_ACTIVATION_BALANCE % preexisting_churn - assert ( - state.consolidation_balance_to_consume == consolidation_churn_limit - remainder - ) - # Check exit epoch - assert state.validators[0].exit_epoch == expected_exit_epoch - - -@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_basic_consolidation_with_compounding_credential(spec, state): - # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - # Set the consolidation balance to consume equal to churn limit - state.consolidation_balance_to_consume = consolidation_churn_limit - 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_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_compounding_withdrawal_credential(spec, state, source_index) - set_compounding_withdrawal_credential(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing(spec, state, signed_consolidation) - - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - # Check consolidation churn is decremented correctly - assert ( - state.consolidation_balance_to_consume - == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE - ) - # Check exit epoch - assert state.validators[0].exit_epoch == expected_exit_epoch - - -@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_consolidation_churn_limit_balance(spec, state): - # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - current_epoch = spec.get_current_epoch(state) - - source_index = spec.get_active_validator_indices(state, current_epoch)[0] - source_validator = state.validators[source_index] - source_validator.effective_balance = consolidation_churn_limit - # Churn limit increases due to higher total balance - updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_compounding_withdrawal_credential(spec, state, source_index) - set_compounding_withdrawal_credential(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing(spec, state, signed_consolidation) - - # validator's effective balance fits into the churn, exit as soon as possible - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - # Check consolidation churn is decremented correctly - assert ( - state.consolidation_balance_to_consume - == updated_consolidation_churn_limit - consolidation_churn_limit - ) - # Check exit epoch - assert state.validators[0].exit_epoch == expected_exit_epoch - - -@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_consolidation_balance_larger_than_churn_limit(spec, state): - # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - current_epoch = spec.get_current_epoch(state) - - source_index = spec.get_active_validator_indices(state, current_epoch)[0] - # Set source balance higher than consolidation churn limit - state.validators[source_index].effective_balance = 2 * consolidation_churn_limit - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_compounding_withdrawal_credential(spec, state, source_index) - set_compounding_withdrawal_credential(spec, state, target_index) - - # Consolidation churn limit increases due to higher total balance - new_churn_limit = spec.get_consolidation_churn_limit(state) - remainder = state.validators[source_index].effective_balance % new_churn_limit - expected_balance = new_churn_limit - remainder - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing(spec, state, signed_consolidation) - - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1 - # Check consolidation churn is decremented correctly - assert state.consolidation_balance_to_consume == expected_balance - # Check exit epoch - assert state.validators[0].exit_epoch == expected_exit_epoch - - -@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_consolidation_balance_through_two_churn_epochs(spec, state): - # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - 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_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_compounding_withdrawal_credential(spec, state, source_index) - set_compounding_withdrawal_credential(spec, state, target_index) - - # Set source balance higher than consolidation churn limit - state.validators[source_index].effective_balance = 3 * consolidation_churn_limit - - new_churn_limit = spec.get_consolidation_churn_limit(state) - remainder = state.validators[source_index].effective_balance % new_churn_limit - expected_balance = new_churn_limit - remainder - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing(spec, state, signed_consolidation) - - # when exiting a multiple of the churn limit greater than 1, an extra exit epoch is added - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 2 - assert state.validators[0].exit_epoch == expected_exit_epoch - # since the earliest exit epoch moves to a new one, consolidation balance is back to full - assert state.consolidation_balance_to_consume == expected_balance - - -# Failing tests - -@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_invalid_source_equals_target(spec, state): - current_epoch = spec.get_current_epoch(state) - validator_index = spec.get_active_validator_indices(state, current_epoch)[0] - validator_privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] - - # Set withdrawal credentials to eth1 - set_eth1_withdrawal_credential_with_balance(spec, state, validator_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, - source_index=validator_index, - target_index=validator_index, - ), - validator_privkey, - validator_privkey, - ) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_exceed_pending_consolidations_limit(spec, state): - state.pending_consolidations = [ - spec.PendingConsolidation(source_index=0, target_index=1) - ] * spec.PENDING_CONSOLIDATIONS_LIMIT - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_not_enough_consolidation_churn_available(spec, state): - state.validators = state.validators[0:2] - state.pending_consolidations = [ - spec.PendingConsolidation(source_index=0, target_index=1) - ] - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_exited_source(spec, state): - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - # exit source - spec.initiate_validator_exit(state, 0) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_exited_target(spec, state): - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - # exit target - spec.initiate_validator_exit(state, 1) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_inactive_source(spec, state): - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - # set source validator as not yet activated - state.validators[0].activation_epoch = spec.FAR_FUTURE_EPOCH - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_inactive_target(spec, state): - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - # set target validator as not yet activated - state.validators[1].activation_epoch = spec.FAR_FUTURE_EPOCH - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_no_execution_withdrawal_credential(spec, state): - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_different_credentials(spec, state): - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - # Set source and target withdrawal credentials to different eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1, address=b"\x10" * 20) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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 -@always_bls -def test_invalid_source_signature(spec, state): - # 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] - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, source_index) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - - # Set earliest consolidation epoch to the expected exit epoch - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - state.earliest_consolidation_epoch = expected_exit_epoch - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - # Set the consolidation balance to consume equal to churn limit - state.consolidation_balance_to_consume = consolidation_churn_limit - - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - - # Change the pubkey of the source validator, invalidating its signature - state.validators[0].pubkey = state.validators[1].pubkey - - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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 -@always_bls -def test_invalid_target_signature(spec, state): - # 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] - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - source_privkey = pubkey_to_privkey[state.validators[source_index].pubkey] - target_privkey = pubkey_to_privkey[state.validators[target_index].pubkey] - - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, source_index) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) - - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation( - epoch=current_epoch, source_index=source_index, target_index=target_index - ), - source_privkey, - target_privkey, - ) - - # Set earliest consolidation epoch to the expected exit epoch - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - state.earliest_consolidation_epoch = expected_exit_epoch - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - # Set the consolidation balance to consume equal to churn limit - state.consolidation_balance_to_consume = consolidation_churn_limit - - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - - # Change the pubkey of the target validator, invalidating its signature - state.validators[1].pubkey = state.validators[2].pubkey - - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=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_invalid_before_specified_epoch(spec, state): - current_epoch = spec.get_current_epoch(state) - source_privkey = pubkey_to_privkey[state.validators[0].pubkey] - target_privkey = pubkey_to_privkey[state.validators[1].pubkey] - # Set source and target withdrawal credentials to the same eth1 credential - set_eth1_withdrawal_credential_with_balance(spec, state, 0) - set_eth1_withdrawal_credential_with_balance(spec, state, 1) - # set epoch=current_epoch + 1, so it's too early to process it - signed_consolidation = sign_consolidation( - spec, - state, - spec.Consolidation(epoch=current_epoch + 1, source_index=0, target_index=1), - source_privkey, - target_privkey, - ) - yield from run_consolidation_processing( - spec, state, signed_consolidation, valid=False - ) diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_execution_layer_consolidation_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_execution_layer_consolidation_request.py new file mode 100644 index 0000000000..5e3f7877cf --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_execution_layer_consolidation_request.py @@ -0,0 +1,809 @@ +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.context import ( + with_electra_and_later, + with_presets, + spec_test, + single_phase, + with_custom_state, + scaled_churn_balances_exceed_activation_exit_churn_limit, + default_activation_threshold, + spec_state_test, +) +from eth2spec.test.helpers.withdrawals import ( + set_eth1_withdrawal_credential_with_balance, + set_compounding_withdrawal_credential, +) + +# *********************** +# * CONSOLIDATION TESTS * +# *********************** + + +@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_basic_consolidation_in_current_consolidation_epoch(spec, state): + # 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] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Set earliest consolidation epoch to the expected exit epoch + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + state.earliest_consolidation_epoch = expected_exit_epoch + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + # Set the consolidation balance to consume equal to churn limit + state.consolidation_balance_to_consume = consolidation_churn_limit + + yield from run_consolidation_processing(spec, state, consolidation) + + # Check consolidation churn is decremented correctly + assert ( + state.consolidation_balance_to_consume + == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE + ) + # Check exit epoch + assert state.validators[source_index].exit_epoch == expected_exit_epoch + + +@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_basic_consolidation_in_new_consolidation_epoch(spec, state): + # 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 + 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] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + yield from run_consolidation_processing(spec, state, consolidation) + + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + # Check consolidation churn is decremented correctly + # consolidation_balance_to_consume is replenished to the churn limit since we move to a new consolidation epoch + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + assert ( + state.consolidation_balance_to_consume + == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE + ) + # Check exit epochs + assert state.validators[source_index].exit_epoch == expected_exit_epoch + + +@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_basic_consolidation_with_preexisting_churn(spec, state): + # 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] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Set earliest consolidation epoch to the expected exit epoch + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + state.earliest_consolidation_epoch = expected_exit_epoch + # Set some nonzero preexisting churn lower than churn limit and sufficient to process the consolidation + preexisting_churn = 2 * spec.MIN_ACTIVATION_BALANCE + state.consolidation_balance_to_consume = preexisting_churn + + yield from run_consolidation_processing(spec, state, consolidation) + + # Check consolidation churn is decremented correctly + assert ( + state.consolidation_balance_to_consume + == preexisting_churn - spec.MIN_ACTIVATION_BALANCE + ) + # Check exit epoch + assert state.validators[source_index].exit_epoch == expected_exit_epoch + + +@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_basic_consolidation_with_insufficient_preexisting_churn(spec, state): + # 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] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Set earliest consolidation epoch to the first available epoch + state.earliest_consolidation_epoch = spec.compute_activation_exit_epoch( + current_epoch + ) + # Set preexisting churn lower than required to process the consolidation + preexisting_churn = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.consolidation_balance_to_consume = preexisting_churn + + yield from run_consolidation_processing(spec, state, consolidation) + + # It takes one more epoch to process the consolidation due to insufficient churn + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1 + # Check consolidation churn is decremented correctly + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + remainder = spec.MIN_ACTIVATION_BALANCE % preexisting_churn + assert ( + state.consolidation_balance_to_consume == consolidation_churn_limit - remainder + ) + # Check exit epoch + assert state.validators[source_index].exit_epoch == expected_exit_epoch + + +@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_basic_consolidation_with_compounding_credentials(spec, state): + # 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] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_compounding_withdrawal_credential( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_compounding_withdrawal_credential(spec, state, target_index) + + # Set the consolidation balance to consume equal to churn limit + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + state.consolidation_balance_to_consume = consolidation_churn_limit + + yield from run_consolidation_processing(spec, state, consolidation) + + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + # Check consolidation churn is decremented correctly + assert ( + state.consolidation_balance_to_consume + == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE + ) + # Check exit epoch + assert state.validators[source_index].exit_epoch == expected_exit_epoch + + +@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_consolidation_churn_limit_balance(spec, state): + # 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] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Set source effective balance to consolidation churn limit + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + state.validators[source_index].effective_balance = consolidation_churn_limit + # Churn limit increases due to higher total balance + updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + + yield from run_consolidation_processing(spec, state, consolidation) + + # validator's effective balance fits into the churn, exit as soon as possible + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + # Check consolidation churn is decremented correctly + assert ( + state.consolidation_balance_to_consume + == updated_consolidation_churn_limit - consolidation_churn_limit + ) + # Check exit epoch + assert state.validators[source_index].exit_epoch == expected_exit_epoch + + +@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_consolidation_balance_larger_than_churn_limit(spec, state): + # 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] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Set source effective balance to 2 * consolidation churn limit + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + state.validators[source_index].effective_balance = 2 * consolidation_churn_limit + + # Consolidation churn limit increases due to higher total balance + updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + remainder = state.validators[source_index].effective_balance % updated_consolidation_churn_limit + expected_balance = updated_consolidation_churn_limit - remainder + + yield from run_consolidation_processing(spec, state, consolidation) + + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1 + # Check consolidation churn is decremented correctly + assert state.consolidation_balance_to_consume == expected_balance + # Check exit epoch + assert state.validators[source_index].exit_epoch == expected_exit_epoch + + +@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_consolidation_balance_through_two_churn_epochs(spec, state): + # 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] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + # Set source balance higher to 3 * consolidation churn limit + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + state.validators[source_index].effective_balance = 3 * consolidation_churn_limit + + new_churn_limit = spec.get_consolidation_churn_limit(state) + remainder = state.validators[source_index].effective_balance % new_churn_limit + expected_balance = new_churn_limit - remainder + + yield from run_consolidation_processing(spec, state, consolidation) + + # when exiting a multiple of the churn limit greater than 1, an extra exit epoch is added + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 2 + assert state.validators[0].exit_epoch == expected_exit_epoch + # since the earliest exit epoch moves to a new one, consolidation balance is back to full + assert state.consolidation_balance_to_consume == expected_balance + + +# Failing tests + +@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_equals_target(spec, state): + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation from source to source + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + + 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_exceed_pending_consolidations_limit(spec, state): + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=0, target_index=1) + ] * spec.PENDING_CONSOLIDATIONS_LIMIT + + # 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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ExecutionLayerConsolidationRequest( + 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) + + yield from run_consolidation_processing( + spec, state, consolidation, success=False + ) + + +@with_electra_and_later +@spec_state_test +@single_phase +def test_incorrect_not_enough_consolidation_churn_available(spec, state): + state.validators = state.validators[0:2] + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=0, target_index=1) + ] + + # 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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ExecutionLayerConsolidationRequest( + 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) + 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_exited_source(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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ExecutionLayerConsolidationRequest( + 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) + + # exit source + spec.initiate_validator_exit(state, source_index) + + 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_exited_target(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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ExecutionLayerConsolidationRequest( + 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) + # exit target + spec.initiate_validator_exit(state, 1) + 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_inactive_source(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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ExecutionLayerConsolidationRequest( + 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) + + # set source validator as not yet activated + state.validators[source_index].activation_epoch = spec.FAR_FUTURE_EPOCH + + 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_inactive_target(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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ExecutionLayerConsolidationRequest( + 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) + + # set target validator as not yet activated + state.validators[1].activation_epoch = spec.FAR_FUTURE_EPOCH + 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_no_source_execution_withdrawal_credential(spec, state): + # Set up a correct consolidation, but source does not have + # an execution withdrawal credential + 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 + consolidation = spec.ExecutionLayerConsolidationRequest( + 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) + 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_no_target_execution_withdrawal_credential(spec, state): + # Set up a correct consolidation, but target does not have + # an execution withdrawal credential + 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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + 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_incorrect_source_address(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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with different source address + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=b"\x33" * 20, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + 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_unknown_source_pubkey(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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with different source pubkey + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=source_address, + source_pubkey=b"\x00" * 48, + target_pubkey=state.validators[target_index].pubkey, + ) + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + 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_unknown_target_pubkey(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 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + # Make consolidation with different target pubkey + consolidation = spec.ExecutionLayerConsolidationRequest( + source_address=b"\x33" * 20, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=b"\x00" * 48, + ) + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + yield from run_consolidation_processing( + spec, state, consolidation, success=False + ) + + +def run_consolidation_processing(spec, state, consolidation, success=True): + """ + Run ``process_consolidation``, yielding: + - pre-state ('pre') + - execution_layer_consolidation_request ('execution_layer_consolidation_request') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + + if success: + validator_pubkeys = [v.pubkey for v in state.validators] + source_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.source_pubkey)) + target_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.target_pubkey)) + source_validator = state.validators[source_index] + target_validator = state.validators[target_index] + pre_exit_epoch_source = source_validator.exit_epoch + pre_exit_epoch_target = target_validator.exit_epoch + pre_pending_consolidations = state.pending_consolidations.copy() + else: + pre_state = state.copy() + + yield 'pre', state + yield 'execution_layer_consolidation_request', consolidation + + spec.process_execution_layer_consolidation_request(state, consolidation) + + yield 'post', state + + if success: + # Check source and target have execution credentials + assert spec.has_execution_withdrawal_credential(source_validator) + assert spec.has_execution_withdrawal_credential(target_validator) + # Check source address in the consolidation fits the withdrawal credentials + assert source_validator.withdrawal_credentials[12:] == consolidation.source_address + # Check source and target are not the same + assert source_index != target_index + # Check source and target were not exiting + assert pre_exit_epoch_source == spec.FAR_FUTURE_EPOCH + assert pre_exit_epoch_target == spec.FAR_FUTURE_EPOCH + # Check source is now exiting + assert state.validators[source_index].exit_epoch < spec.FAR_FUTURE_EPOCH + # Check that the exit epoch matches earliest_consolidation_epoch + assert state.validators[source_index].exit_epoch == state.earliest_consolidation_epoch + # Check that the correct consolidation has been appended + expected_new_pending_consolidation = spec.PendingConsolidation( + source_index=source_index, + target_index=target_index, + ) + assert state.pending_consolidations == pre_pending_consolidations + [expected_new_pending_consolidation] + else: + assert pre_state == state diff --git a/tests/core/pyspec/eth2spec/test/helpers/consolidations.py b/tests/core/pyspec/eth2spec/test/helpers/consolidations.py deleted file mode 100644 index ccdcb9e069..0000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/consolidations.py +++ /dev/null @@ -1,61 +0,0 @@ -from eth2spec.utils import bls -from eth2spec.test.context import expect_assertion_error -from eth2spec.test.helpers.keys import privkeys - - -def prepare_signed_consolidations(spec, state, index_pairs, fork_version=None): - def create_signed_consolidation(source_index, target_index): - consolidation = spec.Consolidation( - epoch=spec.get_current_epoch(state), - source_index=source_index, - target_index=target_index, - ) - return sign_consolidation(spec, state, consolidation, privkeys[source_index], privkeys[target_index], - fork_version=fork_version) - - return [create_signed_consolidation(source_index, target_index) for (source_index, target_index) in index_pairs] - - -def sign_consolidation(spec, state, consolidation, source_privkey, target_privkey, fork_version=None): - domain = spec.compute_domain(spec.DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root) - signing_root = spec.compute_signing_root(consolidation, domain) - return spec.SignedConsolidation( - message=consolidation, - signature=bls.Aggregate([bls.Sign(source_privkey, signing_root), bls.Sign(target_privkey, signing_root)]) - ) - - -def run_consolidation_processing(spec, state, signed_consolidation, valid=True): - """ - Run ``process_consolidation``, yielding: - - pre-state ('pre') - - consolidation ('consolidation') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - - source_validator = state.validators[signed_consolidation.message.source_index] - target_validator = state.validators[signed_consolidation.message.target_index] - - yield 'pre', state - yield 'consolidation', signed_consolidation - - if not valid: - expect_assertion_error(lambda: spec.process_consolidation(state, signed_consolidation)) - yield 'post', None - return - - pre_exit_epoch = source_validator.exit_epoch - - spec.process_consolidation(state, signed_consolidation) - - yield 'post', state - - assert source_validator.withdrawal_credentials[1:] == target_validator.withdrawal_credentials[1:] - assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH - assert state.validators[signed_consolidation.message.source_index].exit_epoch < spec.FAR_FUTURE_EPOCH - assert state.validators[signed_consolidation.message.source_index].exit_epoch == state.earliest_consolidation_epoch - assert state.pending_consolidations[len(state.pending_consolidations) - 1] == spec.PendingConsolidation( - source_index=signed_consolidation.message.source_index, - target_index=signed_consolidation.message.target_index - ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index ef6e2f6442..08c430b621 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -37,6 +37,7 @@ def get_execution_payload_header(spec, execution_payload): if is_post_electra(spec): payload_header.deposit_receipts_root = spec.hash_tree_root(execution_payload.deposit_receipts) payload_header.withdrawal_requests_root = spec.hash_tree_root(execution_payload.withdrawal_requests) + payload_header.consolidation_requests_root = spec.hash_tree_root(execution_payload.consolidation_requests) return payload_header @@ -59,7 +60,8 @@ def compute_el_header_block_hash(spec, transactions_trie_root, withdrawals_trie_root=None, deposit_receipts_trie_root=None, - withdrawal_requests_root=None): + withdrawal_requests_root=None, + consolidation_requests_root=None): """ Computes the RLP execution block hash described by an `ExecutionPayloadHeader`. """ diff --git a/tests/formats/operations/README.md b/tests/formats/operations/README.md index b020b5fd03..d69a704866 100644 --- a/tests/formats/operations/README.md +++ b/tests/formats/operations/README.md @@ -46,7 +46,8 @@ Operations: | `withdrawals` | `ExecutionPayload` | `execution_payload` | `process_withdrawals(state, execution_payload)` (new in Capella) | | `bls_to_execution_change` | `SignedBLSToExecutionChange` | `address_change` | `process_bls_to_execution_change(state, address_change)` (new in Capella) | | `deposit_receipt` | `DepositReceipt` | `deposit_receipt` | `process_deposit_receipt(state, deposit_receipt)` (new in Electra) | -| `exits` | `ExecutionLayerExit` | `execution_layer_exit` | `process_execution_layer_exit(state, execution_layer_exit)` (new in Electra) | +| `execution_layer_withdrawal_request` | `ExecutionLayerWithdrawalRequest` | `execution_layer_withdrawal_request` | `process_execution_layer_withdrawal_request(state, execution_layer_withdrawal_request)` (new in Electra) | +| `execution_layer_consolidation_request` | `ExecutionLayerConsolidationRequest` | `execution_layer_consolidation_request` | `process_execution_layer_consolidation_request(state, execution_layer_consolidation_request)` (new in Electra) | Note that `block_header` is not strictly an operation (and is a full `Block`), but processed in the same manner, and hence included here. diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index 0d203fca6f..d4ca895570 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -45,7 +45,7 @@ _new_electra_mods = {key: 'eth2spec.test.electra.block_processing.test_process_' + key for key in [ 'attestation', - 'consolidation', + 'execution_layer_consolidation_request', 'deposit_receipt', 'execution_layer_withdrawal_request', 'voluntary_exit'