Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,55 @@ def test_basic_consolidation_source_has_less_than_max_effective_balance(spec, st
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_target_has_less_than_min_activation_effective_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]
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)

# Lower the target validator's effective balance
# This shouldn't prevent the consolidation from happening
target_effective_balance = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT
state.validators[target_index].effective_balance = target_effective_balance

# Make consolidation with source address
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)

# Set target to compounding credentials
set_compounding_withdrawal_credential(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 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(
Expand Down Expand Up @@ -947,6 +996,34 @@ def test_incorrect_source_address(spec, state):
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_pubkey_is_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]
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.ConsolidationRequest(
source_address=source_address,
# Use the target's pubkey instead
source_pubkey=state.validators[target_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
set_compounding_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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ def test_process_deposit_request_min_activation(spec, state):
yield from run_deposit_request_processing(spec, state, deposit_request, validator_index)


@with_electra_and_later
@spec_state_test
def test_process_deposit_request_extra_gwei(spec, state):
"""The deposit amount must be at least 1 ETH and must be a multiple of gwei."""
validator_index = len(state.validators)
# An amount with some gwei (the +1 at the end)
amount = spec.EFFECTIVE_BALANCE_INCREMENT + 1
deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True)

yield from run_deposit_request_processing(spec, state, deposit_request, validator_index)

# Ensure the deposit amount is not a multiple of ETH
assert state.pending_deposits[0].amount % spec.EFFECTIVE_BALANCE_INCREMENT != 0


@with_electra_and_later
@spec_state_test
def test_process_deposit_request_max_effective_balance_compounding(spec, state):
Expand All @@ -36,6 +51,27 @@ def test_process_deposit_request_max_effective_balance_compounding(spec, state):
yield from run_deposit_request_processing(spec, state, deposit_request, validator_index)


@with_electra_and_later
@spec_state_test
def test_process_deposit_request_greater_than_max_effective_balance_compounding(spec, state):
validator_index = len(state.validators)
withdrawal_credentials = (
spec.COMPOUNDING_WITHDRAWAL_PREFIX
+ b"\x00" * 11 # specified 0s
+ b"\x59" * 20 # a 20-byte eth1 address
)
deposit_request = prepare_deposit_request(
spec,
validator_index,
# An amount greater than the max effective balance for electra
spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT,
signed=True,
withdrawal_credentials=withdrawal_credentials,
)

yield from run_deposit_request_processing(spec, state, deposit_request, validator_index)


@with_electra_and_later
@spec_state_test
def test_process_deposit_request_top_up_min_activation(spec, state):
Expand All @@ -49,6 +85,20 @@ def test_process_deposit_request_top_up_min_activation(spec, state):
yield from run_deposit_request_processing(spec, state, deposit_request, validator_index)


@with_electra_and_later
@spec_state_test
def test_process_deposit_request_top_up_still_less_than_min_activation(spec, state):
validator_index = 0
amount = spec.EFFECTIVE_BALANCE_INCREMENT
deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True)

balance = 20 * spec.EFFECTIVE_BALANCE_INCREMENT
state.balances[validator_index] = balance
state.validators[validator_index].effective_balance = balance

yield from run_deposit_request_processing(spec, state, deposit_request, validator_index)


@with_electra_and_later
@spec_state_test
def test_process_deposit_request_top_up_max_effective_balance_compounding(spec, state):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,37 @@ def test_exit_existing_churn_and_balance_multiple_of_churn_limit(spec, state):
assert state.earliest_exit_epoch == expected_exit_epoch


@with_electra_and_later
@spec_state_test
def test_voluntary_exit_with_pending_deposit(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH

current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
validator = state.validators[validator_index]
privkey = pubkey_to_privkey[validator.pubkey]

voluntary_exit = spec.VoluntaryExit(
epoch=current_epoch,
validator_index=validator_index,
)
signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, privkey)

# A pending deposit will not prevent an exit
state.pending_deposits = [
spec.PendingDeposit(
pubkey=validator.pubkey,
withdrawal_credentials=validator.withdrawal_credentials,
amount=spec.EFFECTIVE_BALANCE_INCREMENT,
signature=spec.bls.G2_POINT_AT_INFINITY,
slot=spec.GENESIS_SLOT,
)
]

yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit)


@with_electra_and_later
@spec_state_test
def test_invalid_validator_has_pending_withdrawal(spec, state):
Expand Down
161 changes: 161 additions & 0 deletions tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
transition_unsigned_block,
)
from eth2spec.test.context import (
spec_test,
Expand Down Expand Up @@ -422,6 +423,49 @@ def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec,
)


@with_electra_until_eip7732
@spec_state_test
def test_deposit_request_max_per_payload(spec, state):
# signify the eth1 bridge deprecation
state.deposit_requests_start_index = state.eth1_deposit_index

validator_index = len(state.validators)
deposit_requests = []
for i in range(spec.MAX_DEPOSIT_REQUESTS_PER_PAYLOAD):
deposit_requests.append(
prepare_deposit_request(
spec,
validator_index,
spec.EFFECTIVE_BALANCE_INCREMENT,
state.eth1_deposit_index + i,
signed=True,
)
)

# build a block with deposit requests
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_requests.deposits = deposit_requests
block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block)

yield "pre", state

signed_block = state_transition_and_sign_block(spec, state, block)

yield "blocks", [signed_block]
yield "post", state

# check deposit requests are processed correctly
assert len(state.pending_deposits) == len(deposit_requests)
for i, deposit_request in enumerate(block.body.execution_requests.deposits):
assert state.pending_deposits[i] == spec.PendingDeposit(
pubkey=deposit_request.pubkey,
withdrawal_credentials=deposit_request.withdrawal_credentials,
amount=deposit_request.amount,
signature=deposit_request.signature,
slot=signed_block.message.slot,
)


@with_electra_until_eip7732
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
Expand Down Expand Up @@ -786,3 +830,120 @@ def test_withdrawal_requests_when_pending_withdrawal_queue_is_full(spec, state):
assert last_withdrawal.validator_index == index
assert last_withdrawal.amount == withdrawal_request_1.amount
assert withdrawal_request_1.amount != withdrawal_request_2.amount


@with_electra_until_eip7732
@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_multi_epoch_consolidation_chain(spec, state):
"""
This doesn't work the has I had envisioned, but I guess that's a good reason to keep it. When
chaining consolidations like this, the transferred balance is limited by the effective balance
of the source validator, which doesn't update until after all consolidations are processed.
Given that all validators have the same balance, this is effectively a consolidation from the
first validator in the consolidation to the final target validator.
"""

# Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH

# Check that we're at the first slot of the epoch
assert state.slot % spec.SLOTS_PER_EPOCH == 0
current_epoch = spec.get_current_epoch(state)

# This will consolidate 0->1, 1->2, 2->3, ...
consolidation_request_count = 0
for i in range(spec.SLOTS_PER_EPOCH):
consolidation_requests = []
for j in range(0, spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD):
# Setup the source validator
k = i * spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD + j
source_index = spec.get_active_validator_indices(state, current_epoch)[k]
source_address = b"\x11" * 20
set_compounding_withdrawal_credential_with_balance(
spec,
state,
source_index,
effective_balance=spec.MIN_ACTIVATION_BALANCE,
balance=spec.MIN_ACTIVATION_BALANCE,
address=source_address,
)
# Setup the target validator
k = i * spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD + j + 1
target_index = spec.get_active_validator_indices(state, current_epoch)[k]
set_compounding_withdrawal_credential_with_balance(
spec,
state,
target_index,
effective_balance=spec.MIN_ACTIVATION_BALANCE,
balance=spec.MIN_ACTIVATION_BALANCE,
)

# Make the consolidation request
consolidation_requests.append(
spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
)
consolidation_request_count += 1

block = build_empty_block_for_next_slot(spec, state)
block.body.execution_requests.consolidations = consolidation_requests
block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block)
transition_unsigned_block(spec, state, block)

# Check that we're in the next epoch
assert spec.get_current_epoch(state) == current_epoch + 1
# Check that validators at the beginning of the chain are exited
assert len(state.pending_consolidations) == consolidation_request_count
for i in range(consolidation_request_count):
assert state.validators[i].exit_epoch != spec.FAR_FUTURE_EPOCH

# Remove MIN_VALIDATOR_WITHDRAWABILITY_DELAY to speed things up
for i, consolidation in enumerate(state.pending_consolidations):
state.validators[consolidation.source_index].withdrawable_epoch = (
state.validators[consolidation.source_index].exit_epoch + 1
)

# Get the first slot that consolidations will be processed
first_consolidation = state.pending_consolidations[0]
first_slot = (
state.validators[first_consolidation.source_index].withdrawable_epoch * spec.SLOTS_PER_EPOCH
)
# Get the last slot that consolidations will be processed
final_consolidation = state.pending_consolidations[consolidation_request_count - 1]
last_slot = (
state.validators[final_consolidation.source_index].withdrawable_epoch * spec.SLOTS_PER_EPOCH
)

# Transition to the slot/epoch when the first consolidation will be processed
transition_to(spec, state, first_slot - 1)
# Ensure the none of the pending consolidations were processed
assert len(state.pending_consolidations) == consolidation_request_count

yield "pre", state

# Process slots until all pending consolidations are processed
blocks = []
for _ in range(last_slot - first_slot + 1):
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block)
blocks.append(state_transition_and_sign_block(spec, state, block))

yield "blocks", blocks
yield "post", state

# Ensure all pending consolidations have been processed
assert len(state.pending_consolidations) == 0
# Check that the final target validator's effective balance changed.
# The effective balance of the 2nd to last (~32ETH) validator is added to it.
final_target_validator = state.validators[final_consolidation.target_index]
assert final_target_validator.effective_balance > spec.MIN_ACTIVATION_BALANCE
assert final_target_validator.effective_balance <= 2 * spec.MIN_ACTIVATION_BALANCE
Loading