diff --git a/presets/mainnet/electra.yaml b/presets/mainnet/electra.yaml index bb97cfa445..a1a3501277 100644 --- a/presets/mainnet/electra.yaml +++ b/presets/mainnet/electra.yaml @@ -10,7 +10,7 @@ MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 # State list lengths # --------------------------------------------------------------- # `uint64(2**27)` (= 134,217,728) -PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 +PENDING_DEPOSITS_LIMIT: 134217728 # `uint64(2**27)` (= 134,217,728) PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 # `uint64(2**18)` (= 262,144) @@ -43,3 +43,8 @@ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 # --------------------------------------------------------------- # 2**3 ( = 8) pending withdrawals MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 + +# Pending deposits processing +# --------------------------------------------------------------- +# 2**4 ( = 4) pending deposits +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 diff --git a/presets/minimal/electra.yaml b/presets/minimal/electra.yaml index bef11551e9..1656491655 100644 --- a/presets/minimal/electra.yaml +++ b/presets/minimal/electra.yaml @@ -10,7 +10,7 @@ MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 # State list lengths # --------------------------------------------------------------- # `uint64(2**27)` (= 134,217,728) -PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 +PENDING_DEPOSITS_LIMIT: 134217728 # [customized] `uint64(2**6)` (= 64) PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 # [customized] `uint64(2**6)` (= 64) @@ -43,3 +43,8 @@ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2 # --------------------------------------------------------------- # 2**1 ( = 2) pending withdrawals MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2 + +# Pending deposits processing +# --------------------------------------------------------------- +# 2**4 ( = 4) pending deposits +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 diff --git a/specs/_features/eip7732/beacon-chain.md b/specs/_features/eip7732/beacon-chain.md index ec38d41674..30651cedbe 100644 --- a/specs/_features/eip7732/beacon-chain.md +++ b/specs/_features/eip7732/beacon-chain.md @@ -270,7 +270,7 @@ class BeaconState(Container): earliest_exit_epoch: Epoch consolidation_balance_to_consume: Gwei earliest_consolidation_epoch: Epoch - pending_balance_deposits: List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT] + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] # PBS diff --git a/specs/_features/eip7732/fork.md b/specs/_features/eip7732/fork.md index 942b9c30cc..120e64fa46 100644 --- a/specs/_features/eip7732/fork.md +++ b/specs/_features/eip7732/fork.md @@ -126,7 +126,7 @@ def upgrade_to_eip7732(pre: electra.BeaconState) -> BeaconState: earliest_exit_epoch=pre.earliest_exit_epoch, consolidation_balance_to_consume=pre.consolidation_balance_to_consume, earliest_consolidation_epoch=pre.earliest_consolidation_epoch, - pending_balance_deposits=pre.pending_balance_deposits, + pending_deposits=pre.pending_deposits, pending_partial_withdrawals=pre.pending_partial_withdrawals, pending_consolidations=pre.pending_consolidations, # ePBS diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index 111dc5b970..1362021e7f 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -19,12 +19,13 @@ - [Max operations per block](#max-operations-per-block) - [Execution](#execution) - [Withdrawals processing](#withdrawals-processing) + - [Pending deposits processing](#pending-deposits-processing) - [Configuration](#configuration) - [Validator cycle](#validator-cycle) - [Containers](#containers) - [New containers](#new-containers) - [`DepositRequest`](#depositrequest) - - [`PendingBalanceDeposit`](#pendingbalancedeposit) + - [`PendingDeposit`](#pendingdeposit) - [`PendingPartialWithdrawal`](#pendingpartialwithdrawal) - [`WithdrawalRequest`](#withdrawalrequest) - [`ConsolidationRequest`](#consolidationrequest) @@ -68,7 +69,8 @@ - [Modified `process_epoch`](#modified-process_epoch) - [Modified `process_registry_updates`](#modified-process_registry_updates) - [Modified `process_slashings`](#modified-process_slashings) - - [New `process_pending_balance_deposits`](#new-process_pending_balance_deposits) + - [New `apply_pending_deposit`](#new-apply_pending_deposit) + - [New `process_pending_deposits`](#new-process_pending_deposits) - [New `process_pending_consolidations`](#new-process_pending_consolidations) - [Modified `process_effective_balance_updates`](#modified-process_effective_balance_updates) - [Execution engine](#execution-engine) @@ -88,10 +90,11 @@ - [Attestations](#attestations) - [Modified `process_attestation`](#modified-process_attestation) - [Deposits](#deposits) + - [Modified `get_validator_from_deposit`](#modified-get_validator_from_deposit) + - [Modified `add_validator_to_registry`](#modified-add_validator_to_registry) - [Modified `apply_deposit`](#modified-apply_deposit) - [New `is_valid_deposit_signature`](#new-is_valid_deposit_signature) - - [Modified `add_validator_to_registry`](#modified-add_validator_to_registry) - - [Modified `get_validator_from_deposit`](#modified-get_validator_from_deposit) + - [Modified `process_deposit`](#modified-process_deposit) - [Voluntary exits](#voluntary-exits) - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit) - [Execution layer withdrawal requests](#execution-layer-withdrawal-requests) @@ -153,7 +156,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Unit | | - | - | :-: | -| `PENDING_BALANCE_DEPOSITS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending balance deposits | +| `PENDING_DEPOSITS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending deposits | | `PENDING_PARTIAL_WITHDRAWALS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending partial withdrawals | | `PENDING_CONSOLIDATIONS_LIMIT` | `uint64(2**18)` (= 262,144) | pending consolidations | @@ -178,6 +181,12 @@ The following values are (non-configurable) constants used throughout the specif | - | - | - | | `MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP` | `uint64(2**3)` (= 8)| *[New in Electra:EIP7002]* Maximum number of pending partial withdrawals to process per payload | +### Pending deposits processing + +| Name | Value | Description | +| - | - | - | +| `MAX_PENDING_DEPOSITS_PER_EPOCH` | `uint64(2**4)` (= 16)| *[New in Electra:EIP6110]* Maximum number of pending deposits to process per epoch | + ## Configuration ### Validator cycle @@ -204,14 +213,17 @@ class DepositRequest(Container): index: uint64 ``` -#### `PendingBalanceDeposit` +#### `PendingDeposit` *Note*: The container is new in EIP7251. ```python -class PendingBalanceDeposit(Container): - index: ValidatorIndex +class PendingDeposit(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 amount: Gwei + signature: BLSSignature + slot: Slot ``` #### `PendingPartialWithdrawal` @@ -373,7 +385,7 @@ class BeaconState(Container): earliest_exit_epoch: Epoch # [New in Electra:EIP7251] consolidation_balance_to_consume: Gwei # [New in Electra:EIP7251] earliest_consolidation_epoch: Epoch # [New in Electra:EIP7251] - pending_balance_deposits: List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT] # [New in Electra:EIP7251] + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] # [New in Electra:EIP7251] # [New in Electra:EIP7251] pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] # [New in Electra:EIP7251] @@ -640,9 +652,16 @@ def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> No if balance > MIN_ACTIVATION_BALANCE: excess_balance = balance - MIN_ACTIVATION_BALANCE state.balances[index] = MIN_ACTIVATION_BALANCE - state.pending_balance_deposits.append( - PendingBalanceDeposit(index=index, amount=excess_balance) - ) + validator = state.validators[index] + # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + # and GENESIS_SLOT to distinguish from a pending deposit request + state.pending_deposits.append(PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=excess_balance, + signature=bls.G2_POINT_AT_INFINITY, + slot=GENESIS_SLOT, + )) ``` #### New `compute_exit_epoch_and_update_churn` @@ -746,7 +765,7 @@ def process_epoch(state: BeaconState) -> None: process_registry_updates(state) # [Modified in Electra:EIP7251] process_slashings(state) # [Modified in Electra:EIP7251] process_eth1_data_reset(state) - process_pending_balance_deposits(state) # [New in Electra:EIP7251] + process_pending_deposits(state) # [New in Electra:EIP7251] process_pending_consolidations(state) # [New in Electra:EIP7251] process_effective_balance_updates(state) # [Modified in Electra:EIP7251] process_slashings_reset(state) @@ -803,45 +822,100 @@ def process_slashings(state: BeaconState) -> None: decrease_balance(state, ValidatorIndex(index), penalty) ``` -#### New `process_pending_balance_deposits` +#### New `apply_pending_deposit` ```python -def process_pending_balance_deposits(state: BeaconState) -> None: +def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> None: + """ + Applies ``deposit`` to the ``state``. + """ + validator_pubkeys = [v.pubkey for v in state.validators] + if deposit.pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + if is_valid_deposit_signature( + deposit.pubkey, + deposit.withdrawal_credentials, + deposit.amount, + deposit.signature + ): + add_validator_to_registry(state, deposit.pubkey, deposit.withdrawal_credentials, deposit.amount) + else: + validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) + # Increase balance + increase_balance(state, validator_index, deposit.amount) +``` + +#### New `process_pending_deposits` + +Iterating over `pending_deposits` queue this function runs the following checks before applying pending deposit: +1. All Eth1 bridge deposits are processed before the first deposit request gets processed. +2. Deposit position in the queue is finalized. +3. Deposit does not exceed the `MAX_PENDING_DEPOSITS_PER_EPOCH` limit. +4. Deposit does not exceed the activation churn limit. + +```python +def process_pending_deposits(state: BeaconState) -> None: next_epoch = Epoch(get_current_epoch(state) + 1) available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(state) processed_amount = 0 next_deposit_index = 0 deposits_to_postpone = [] + is_churn_limit_reached = False + finalized_slot = compute_start_slot_at_epoch(state.finalized_checkpoint.epoch) + + for deposit in state.pending_deposits: + # Do not process deposit requests if Eth1 bridge deposits are not yet applied. + if ( + # Is deposit request + deposit.slot > GENESIS_SLOT and + # There are pending Eth1 bridge deposits + state.eth1_deposit_index < state.deposit_requests_start_index + ): + break + + # Check if deposit has been finalized, otherwise, stop processing. + if deposit.slot > finalized_slot: + break + + # Check if number of processed deposits has not reached the limit, otherwise, stop processing. + if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH: + break - for deposit in state.pending_balance_deposits: - validator = state.validators[deposit.index] - # Validator is exiting, postpone the deposit until after withdrawable epoch - if validator.exit_epoch < FAR_FUTURE_EPOCH: - if next_epoch <= validator.withdrawable_epoch: - deposits_to_postpone.append(deposit) + # Read validator state + is_validator_exited = False + is_validator_withdrawn = False + validator_pubkeys = [v.pubkey for v in state.validators] + if deposit.pubkey in validator_pubkeys: + validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))] + is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH + is_validator_withdrawn = validator.withdrawable_epoch < next_epoch + + if is_validator_withdrawn: # Deposited balance will never become active. Increase balance but do not consume churn - else: - increase_balance(state, deposit.index, deposit.amount) - # Validator is not exiting, attempt to process deposit + apply_pending_deposit(state, deposit) + elif is_validator_exited: + # Validator is exiting, postpone the deposit until after withdrawable epoch + deposits_to_postpone.append(deposit) else: - # Deposit does not fit in the churn, no more deposit processing in this epoch. - if processed_amount + deposit.amount > available_for_processing: + # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. + is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing + if is_churn_limit_reached: break - # Deposit fits in the churn, process it. Increase balance and consume churn. - else: - increase_balance(state, deposit.index, deposit.amount) - processed_amount += deposit.amount + + # Consume churn and apply deposit. + processed_amount += deposit.amount + apply_pending_deposit(state, deposit) + # Regardless of how the deposit was handled, we move on in the queue. next_deposit_index += 1 - state.pending_balance_deposits = state.pending_balance_deposits[next_deposit_index:] + state.pending_deposits = state.pending_deposits[next_deposit_index:] + deposits_to_postpone - if len(state.pending_balance_deposits) == 0: - state.deposit_balance_to_consume = Gwei(0) - else: + # Accumulate churn only if the churn limit has been hit. + if is_churn_limit_reached: state.deposit_balance_to_consume = available_for_processing - processed_amount - - state.pending_balance_deposits += deposits_to_postpone + else: + state.deposit_balance_to_consume = Gwei(0) ``` #### New `process_pending_consolidations` @@ -1193,6 +1267,47 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ##### Deposits +###### Modified `get_validator_from_deposit` + +*Note*: The function is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` for compounding withdrawal credential. + +```python +def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64) -> Validator: + validator = Validator( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=Gwei(0), + ) + + # [Modified in Electra:EIP7251] + max_effective_balance = get_max_effective_balance(validator) + validator.effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance) + + return validator +``` + +###### Modified `add_validator_to_registry` + +*Note*: The function `add_validator_to_registry` is modified to use the modified `get_validator_from_deposit`. + +```python +def add_validator_to_registry(state: BeaconState, + pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64) -> None: + index = get_index_for_new_validator(state) + validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) # [Modified in Electra:EIP7251] + set_or_append_list(state.validators, index, validator) + set_or_append_list(state.balances, index, amount) + set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.inactivity_scores, index, uint64(0)) +``` + ###### Modified `apply_deposit` *Note*: The function `apply_deposit` is modified to support EIP7251. @@ -1207,13 +1322,25 @@ def apply_deposit(state: BeaconState, if pubkey not in validator_pubkeys: # Verify the deposit signature (proof of possession) which is not checked by the deposit contract if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature): - add_validator_to_registry(state, pubkey, withdrawal_credentials, amount) + add_validator_to_registry(state, pubkey, withdrawal_credentials, Gwei(0)) # [Modified in Electra:EIP7251] + # [New in Electra:EIP7251] + state.pending_deposits.append(PendingDeposit( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + signature=signature, + slot=GENESIS_SLOT, # Use GENESIS_SLOT to distinguish from a pending deposit request + )) else: # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - state.pending_balance_deposits.append( - PendingBalanceDeposit(index=index, amount=amount) - ) # [Modified in Electra:EIP7251] + # [Modified in Electra:EIP7251] + state.pending_deposits.append(PendingDeposit( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + signature=signature, + slot=GENESIS_SLOT # Use GENESIS_SLOT to distinguish from a pending deposit request + )) ``` ###### New `is_valid_deposit_signature` @@ -1233,39 +1360,31 @@ def is_valid_deposit_signature(pubkey: BLSPubkey, return bls.Verify(pubkey, signing_root, signature) ``` -###### Modified `add_validator_to_registry` +###### Modified `process_deposit` -*Note*: The function `add_validator_to_registry` is modified to initialize the validator with a balance of zero and add a pending balance deposit to the queue. +*Note*: The function `process_deposit` is modified to to use the modified `apply_deposit`. ```python -def add_validator_to_registry(state: BeaconState, - pubkey: BLSPubkey, - withdrawal_credentials: Bytes32, - amount: uint64) -> None: - index = get_index_for_new_validator(state) - validator = get_validator_from_deposit(pubkey, withdrawal_credentials) - set_or_append_list(state.validators, index, validator) - set_or_append_list(state.balances, index, 0) # [Modified in Electra:EIP7251] - set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) - set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) - set_or_append_list(state.inactivity_scores, index, uint64(0)) - state.pending_balance_deposits.append(PendingBalanceDeposit(index=index, amount=amount)) # [New in Electra:EIP7251] -``` - -###### Modified `get_validator_from_deposit` +def process_deposit(state: BeaconState, deposit: Deposit) -> None: + # Verify the Merkle branch + assert is_valid_merkle_branch( + leaf=hash_tree_root(deposit.data), + branch=deposit.proof, + depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in + index=state.eth1_deposit_index, + root=state.eth1_data.deposit_root, + ) -*Note*: The function `get_validator_from_deposit` is modified to initialize the validator with an effective balance of zero. + # Deposits must be processed in order + state.eth1_deposit_index += 1 -```python -def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32) -> Validator: - return Validator( - pubkey=pubkey, - withdrawal_credentials=withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=0, # [Modified in Electra:EIP7251] + # [Modified in Electra:EIP7251] + apply_deposit( + state=state, + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + amount=deposit.data.amount, + signature=deposit.data.signature, ) ``` @@ -1374,13 +1493,14 @@ def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUESTS_START_INDEX: state.deposit_requests_start_index = deposit_request.index - apply_deposit( - state=state, + # Create pending deposit + state.pending_deposits.append(PendingDeposit( pubkey=deposit_request.pubkey, withdrawal_credentials=deposit_request.withdrawal_credentials, amount=deposit_request.amount, signature=deposit_request.signature, - ) + slot=state.slot, + )) ``` ##### Execution layer consolidation requests @@ -1539,9 +1659,11 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, process_deposit(state, deposit) # Process deposit balance updates - for deposit in state.pending_balance_deposits: - increase_balance(state, deposit.index, deposit.amount) - state.pending_balance_deposits = [] + validator_pubkeys = [v.pubkey for v in state.validators] + for deposit in state.pending_deposits: + validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) + increase_balance(state, validator_index, deposit.amount) + state.pending_deposits = [] # Process activations for index, validator in enumerate(state.validators): diff --git a/specs/electra/fork.md b/specs/electra/fork.md index b1e5da1252..6ac5be5b03 100644 --- a/specs/electra/fork.md +++ b/specs/electra/fork.md @@ -133,7 +133,7 @@ def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: earliest_exit_epoch=earliest_exit_epoch, consolidation_balance_to_consume=0, earliest_consolidation_epoch=compute_activation_exit_epoch(get_current_epoch(pre)), - pending_balance_deposits=[], + pending_deposits=[], pending_partial_withdrawals=[], pending_consolidations=[], ) @@ -157,9 +157,15 @@ def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: validator = post.validators[index] validator.effective_balance = 0 validator.activation_eligibility_epoch = FAR_FUTURE_EPOCH - post.pending_balance_deposits.append( - PendingBalanceDeposit(index=index, amount=balance) - ) + # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + # and GENESIS_SLOT to distinguish from a pending deposit request + post.pending_deposits.append(PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=balance, + signature=bls.G2_POINT_AT_INFINITY, + slot=GENESIS_SLOT, + )) # Ensure early adopters of compounding credentials go through the activation churn for index, validator in enumerate(post.validators): diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_deposit.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_deposit.py index 4f2176ca26..1abb93e62c 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_deposit.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_deposit.py @@ -32,10 +32,13 @@ def test_success_top_up_to_withdrawn_validator(spec, state): yield from run_deposit_processing(spec, state, deposit, validator_index) if is_post_electra(spec): - pending_balance_deposits_len = len(state.pending_balance_deposits) - pending_balance_deposit = state.pending_balance_deposits[pending_balance_deposits_len - 1] - assert pending_balance_deposit.amount == amount - assert pending_balance_deposit.index == validator_index + pending_deposits_len = len(state.pending_deposits) + pending_deposit = state.pending_deposits[pending_deposits_len - 1] + assert pending_deposit.pubkey == deposit.data.pubkey + assert pending_deposit.withdrawal_credentials == deposit.data.withdrawal_credentials + assert pending_deposit.amount == deposit.data.amount + assert pending_deposit.signature == deposit.data.signature + assert pending_deposit.slot == spec.GENESIS_SLOT else: assert state.balances[validator_index] == amount assert state.validators[validator_index].effective_balance == 0 @@ -47,7 +50,7 @@ def test_success_top_up_to_withdrawn_validator(spec, state): if is_post_electra(spec): has_execution_withdrawal = spec.has_execution_withdrawal_credential(validator) is_withdrawable = validator.withdrawable_epoch <= current_epoch - has_non_zero_balance = pending_balance_deposit.amount > 0 + has_non_zero_balance = pending_deposit.amount > 0 # NOTE: directly compute `is_fully_withdrawable_validator` conditions here # to work around how the epoch processing changed balance updates assert has_execution_withdrawal and is_withdrawable and has_non_zero_balance diff --git a/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py index dabc2be18b..406fde341f 100644 --- a/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py @@ -365,8 +365,11 @@ def test_top_up_and_partial_withdrawable_validator(spec, state): yield 'post', state if is_post_electra(spec): - assert state.pending_balance_deposits[0].amount == amount - assert state.pending_balance_deposits[0].index == validator_index + assert state.pending_deposits[0].pubkey == deposit.data.pubkey + assert state.pending_deposits[0].withdrawal_credentials == deposit.data.withdrawal_credentials + assert state.pending_deposits[0].amount == deposit.data.amount + assert state.pending_deposits[0].signature == deposit.data.signature + assert state.pending_deposits[0].slot == spec.GENESIS_SLOT else: # Since withdrawals happen before deposits, it becomes partially withdrawable after state transition. validator = state.validators[validator_index] @@ -405,7 +408,7 @@ def test_top_up_to_fully_withdrawn_validator(spec, state): balance = state.balances[validator_index] if is_post_electra(spec): - balance += state.pending_balance_deposits[0].amount + balance += state.pending_deposits[0].amount assert spec.is_fully_withdrawable_validator( state.validators[validator_index], diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py index 426f4fbe79..49744946f0 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py @@ -1148,12 +1148,19 @@ def run_consolidation_processing(spec, state, consolidation, success=True): assert state.pending_consolidations == pre_pending_consolidations + [expected_new_pending_consolidation] # Check excess balance is queued if the target switched to compounding if pre_target_withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX: - assert state.validators[target_index].withdrawal_credentials == ( - spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_target_withdrawal_credentials[1:]) + post_target_withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_target_withdrawal_credentials[1:] + ) + assert state.validators[target_index].withdrawal_credentials == post_target_withdrawal_credentials assert state.balances[target_index] == spec.MIN_ACTIVATION_BALANCE if pre_target_balance > spec.MIN_ACTIVATION_BALANCE: - assert state.pending_balance_deposits == [spec.PendingBalanceDeposit( - index=target_index, amount=(pre_target_balance - spec.MIN_ACTIVATION_BALANCE))] + assert len(state.pending_deposits) == 1 + pending_deposit = state.pending_deposits[0] + assert pending_deposit.pubkey == target_validator.pubkey + assert pending_deposit.withdrawal_credentials == post_target_withdrawal_credentials + assert pending_deposit.amount == (pre_target_balance - spec.MIN_ACTIVATION_BALANCE) + assert pending_deposit.signature == spec.G2_POINT_AT_INFINITY + assert pending_deposit.slot == spec.GENESIS_SLOT else: assert state.balances[target_index] == pre_target_balance else: @@ -1194,14 +1201,20 @@ def run_switch_to_compounding_processing(spec, state, consolidation, success=Tru # Check source address in the consolidation fits the withdrawal credentials assert state.validators[source_index].withdrawal_credentials[12:] == consolidation.source_address # Check that the source has switched to compounding - assert state.validators[source_index].withdrawal_credentials == ( + post_withdrawal_credentials = ( spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_withdrawal_credentials[1:] ) + assert state.validators[source_index].withdrawal_credentials == post_withdrawal_credentials # Check excess balance is queued assert state.balances[source_index] == spec.MIN_ACTIVATION_BALANCE if pre_balance > spec.MIN_ACTIVATION_BALANCE: - assert state.pending_balance_deposits == [spec.PendingBalanceDeposit( - index=source_index, amount=(pre_balance - spec.MIN_ACTIVATION_BALANCE))] + assert len(state.pending_deposits) == 1 + pending_deposit = state.pending_deposits[0] + assert pending_deposit.pubkey == source_validator.pubkey + assert pending_deposit.withdrawal_credentials == post_withdrawal_credentials + assert pending_deposit.amount == (pre_balance - spec.MIN_ACTIVATION_BALANCE) + assert pending_deposit.signature == spec.G2_POINT_AT_INFINITY + assert pending_deposit.slot == spec.GENESIS_SLOT # Check no consolidation has been initiated assert state.validators[source_index].exit_epoch == spec.FAR_FUTURE_EPOCH assert state.pending_consolidations == pre_pending_consolidations diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_deposit_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_deposit_request.py index 4b96200e45..8c90592a75 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_deposit_request.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_deposit_request.py @@ -2,43 +2,16 @@ from eth2spec.test.helpers.deposits import ( prepare_deposit_request, run_deposit_request_processing, - run_deposit_request_processing_with_specific_fork_version ) -from eth2spec.test.helpers.state import next_epoch_via_block -from eth2spec.test.helpers.withdrawals import set_validator_fully_withdrawable @with_electra_and_later @spec_state_test -def test_new_deposit_under_max(spec, state): - # fresh deposit = next validator index = validator appended to registry - validator_index = len(state.validators) - # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement. - amount = spec.MAX_EFFECTIVE_BALANCE - 1 - deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) - - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - - -@with_electra_and_later -@spec_state_test -def test_new_deposit_max(spec, state): +def test_process_deposit_request_min_activation(spec, state): # fresh deposit = next validator index = validator appended to registry validator_index = len(state.validators) # effective balance will be exactly the same as balance. - amount = spec.MAX_EFFECTIVE_BALANCE - deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) - - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - - -@with_electra_and_later -@spec_state_test -def test_new_deposit_over_max(spec, state): - # fresh deposit = next validator index = validator appended to registry - validator_index = len(state.validators) - # just 1 over the limit, effective balance should be set MAX_EFFECTIVE_BALANCE during processing - amount = spec.MAX_EFFECTIVE_BALANCE + 1 + amount = spec.MIN_ACTIVATION_BALANCE deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) @@ -46,42 +19,22 @@ def test_new_deposit_over_max(spec, state): @with_electra_and_later @spec_state_test -def test_new_deposit_eth1_withdrawal_credentials(spec, state): +def test_process_deposit_request_max_effective_balance_compounding(spec, state): # fresh deposit = next validator index = validator appended to registry validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA withdrawal_credentials = ( - spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + spec.COMPOUNDING_WITHDRAWAL_PREFIX + b'\x00' * 11 # specified 0s + b'\x59' * 20 # a 20-byte eth1 address ) - amount = spec.MAX_EFFECTIVE_BALANCE - deposit_request = prepare_deposit_request( - spec, - validator_index, - amount, - withdrawal_credentials=withdrawal_credentials, - signed=True, - ) - - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - - -@with_electra_and_later -@spec_state_test -def test_new_deposit_non_versioned_withdrawal_credentials(spec, state): - # fresh deposit = next validator index = validator appended to registry - validator_index = len(state.validators) - withdrawal_credentials = ( - b'\xFF' # Non specified withdrawal credentials version - + b'\x02' * 31 # Garabage bytes - ) - amount = spec.MAX_EFFECTIVE_BALANCE deposit_request = prepare_deposit_request( spec, validator_index, amount, - withdrawal_credentials=withdrawal_credentials, signed=True, + withdrawal_credentials=withdrawal_credentials ) yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) @@ -89,205 +42,100 @@ def test_new_deposit_non_versioned_withdrawal_credentials(spec, state): @with_electra_and_later @spec_state_test -@always_bls -def test_correct_sig_but_forked_state(spec, state): - validator_index = len(state.validators) - amount = spec.MAX_EFFECTIVE_BALANCE - # deposits will always be valid, regardless of the current fork - state.fork.current_version = spec.Version('0x1234abcd') - deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - - -@with_electra_and_later -@spec_state_test -@always_bls -def test_incorrect_sig_new_deposit(spec, state): - # fresh deposit = next validator index = validator appended to registry - validator_index = len(state.validators) - amount = spec.MAX_EFFECTIVE_BALANCE - deposit_request = prepare_deposit_request(spec, validator_index, amount) - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index, effective=False) - - -@with_electra_and_later -@spec_state_test -def test_top_up__max_effective_balance(spec, state): +def test_process_deposit_request_top_up_min_activation(spec, state): validator_index = 0 - amount = spec.MAX_EFFECTIVE_BALANCE // 4 + amount = spec.MIN_ACTIVATION_BALANCE // 4 deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) - state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE - state.validators[validator_index].effective_balance = spec.MAX_EFFECTIVE_BALANCE + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - deposits_len = len(state.pending_balance_deposits) - assert state.pending_balance_deposits[deposits_len - 1].amount == amount - assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE - @with_electra_and_later @spec_state_test -def test_top_up__less_effective_balance(spec, state): +def test_process_deposit_request_top_up_max_effective_balance_compounding(spec, state): validator_index = 0 - amount = spec.MAX_EFFECTIVE_BALANCE // 4 - deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) - - initial_balance = spec.MAX_EFFECTIVE_BALANCE - 1000 - initial_effective_balance = spec.MAX_EFFECTIVE_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT - state.balances[validator_index] = initial_balance - state.validators[validator_index].effective_balance = initial_effective_balance - - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - - deposits_len = len(state.pending_balance_deposits) - assert state.pending_balance_deposits[deposits_len - 1].amount == amount - # unchanged effective balance - assert state.validators[validator_index].effective_balance == initial_effective_balance - - -@with_electra_and_later -@spec_state_test -def test_top_up__zero_balance(spec, state): - validator_index = 0 - amount = spec.MAX_EFFECTIVE_BALANCE // 4 - deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) - - initial_balance = 0 - initial_effective_balance = 0 - state.balances[validator_index] = initial_balance - state.validators[validator_index].effective_balance = initial_effective_balance - - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - - deposits_len = len(state.pending_balance_deposits) - assert state.pending_balance_deposits[deposits_len - 1].amount == amount - # unchanged effective balance - assert state.validators[validator_index].effective_balance == initial_effective_balance - - -@with_electra_and_later -@spec_state_test -@always_bls -def test_incorrect_sig_top_up(spec, state): - validator_index = 0 - amount = spec.MAX_EFFECTIVE_BALANCE // 4 - deposit_request = prepare_deposit_request(spec, validator_index, amount) - - # invalid signatures, in top-ups, are allowed! - yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + amount = spec.MIN_ACTIVATION_BALANCE // 4 + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE + state.validators[validator_index].effective_balance = spec.MAX_EFFECTIVE_BALANCE + state.validators[validator_index].withdrawal_credentials = withdrawal_credentials -@with_electra_and_later -@spec_state_test -def test_incorrect_withdrawal_credentials_top_up(spec, state): - validator_index = 0 - amount = spec.MAX_EFFECTIVE_BALANCE // 4 - withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(b"junk")[1:] deposit_request = prepare_deposit_request( spec, validator_index, amount, + signed=True, withdrawal_credentials=withdrawal_credentials ) - # inconsistent withdrawal credentials, in top-ups, are allowed! yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) @with_electra_and_later @spec_state_test -def test_key_validate_invalid_subgroup(spec, state): +@always_bls +def test_process_deposit_request_invalid_sig(spec, state): + # fresh deposit = next validator index = validator appended to registry validator_index = len(state.validators) - amount = spec.MAX_EFFECTIVE_BALANCE - - # All-zero pubkey would not pass `bls.KeyValidate`, but `process_deposit` would not throw exception. - pubkey = b'\x00' * 48 - - deposit_request = prepare_deposit_request(spec, validator_index, amount, pubkey=pubkey, signed=True) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(spec, validator_index, amount) yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) @with_electra_and_later @spec_state_test -def test_key_validate_invalid_decompression(spec, state): - validator_index = len(state.validators) - amount = spec.MAX_EFFECTIVE_BALANCE - - # `deserialization_fails_infinity_with_true_b_flag` BLS G1 deserialization test case. - # This pubkey would not pass `bls.KeyValidate`, but `process_deposit` would not throw exception. - pubkey_hex = 'c01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - pubkey = bytes.fromhex(pubkey_hex) +@always_bls +def test_process_deposit_request_top_up_invalid_sig(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + deposit_request = prepare_deposit_request(spec, validator_index, amount) - deposit_request = prepare_deposit_request(spec, validator_index, amount, pubkey=pubkey, signed=True) + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) @with_electra_and_later @spec_state_test -@always_bls -def test_ineffective_deposit_with_previous_fork_version(spec, state): - # Since deposits are valid across forks, the domain is always set with `GENESIS_FORK_VERSION`. - # It's an ineffective deposit because it fails at BLS sig verification. - # NOTE: it was effective in Altair. - assert state.fork.previous_version != state.fork.current_version - - yield from run_deposit_request_processing_with_specific_fork_version( - spec, - state, - fork_version=state.fork.previous_version, - effective=False, - ) +def test_process_deposit_request_set_start_index(spec, state): + assert state.deposit_requests_start_index == spec.UNSET_DEPOSIT_REQUESTS_START_INDEX + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) -@with_electra_and_later -@spec_state_test -@always_bls -def test_effective_deposit_with_genesis_fork_version(spec, state): - assert spec.config.GENESIS_FORK_VERSION not in (state.fork.previous_version, state.fork.current_version) + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - yield from run_deposit_request_processing_with_specific_fork_version( - spec, - state, - fork_version=spec.config.GENESIS_FORK_VERSION, - ) + assert state.deposit_requests_start_index == deposit_request.index @with_electra_and_later @spec_state_test -def test_success_top_up_to_withdrawn_validator(spec, state): - validator_index = 0 +def test_process_deposit_request_set_start_index_only_once(spec, state): + initial_start_index = 1 - # Fully withdraw validator - set_validator_fully_withdrawable(spec, state, validator_index) - assert state.balances[validator_index] > 0 - next_epoch_via_block(spec, state) - assert state.balances[validator_index] == 0 - assert state.validators[validator_index].effective_balance > 0 - next_epoch_via_block(spec, state) - assert state.validators[validator_index].effective_balance == 0 + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) - # Make a top-up balance to validator - amount = spec.MAX_EFFECTIVE_BALANCE // 4 - deposit_request = prepare_deposit_request(spec, validator_index, amount, len(state.validators), signed=True) + assert initial_start_index != deposit_request.index + state.deposit_requests_start_index = initial_start_index yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) - deposits_len = len(state.pending_balance_deposits) - assert state.pending_balance_deposits[deposits_len - 1].amount == amount - assert state.validators[validator_index].effective_balance == 0 - - validator = state.validators[validator_index] - - pending_balance_deposits_len = len(state.pending_balance_deposits) - pending_balance_deposit = state.pending_balance_deposits[pending_balance_deposits_len - 1] - current_epoch = spec.get_current_epoch(state) - has_execution_withdrawal = spec.has_execution_withdrawal_credential(validator) - is_withdrawable = validator.withdrawable_epoch <= current_epoch - has_non_zero_balance = pending_balance_deposit.amount > 0 - # NOTE: directly compute `is_fully_withdrawable_validator` conditions here - # to work around how the epoch processing changed balance updates - assert has_execution_withdrawal and is_withdrawable and has_non_zero_balance + assert state.deposit_requests_start_index == initial_start_index diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/__init__.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/__init__.py new file mode 100644 index 0000000000..de7612a767 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/__init__.py @@ -0,0 +1,3 @@ +# This is a trick to allow tests be split into multiple files and use the same test format. +from .test_apply_pending_deposit import * # noqa: F401 F403 +from .test_process_pending_deposits import * # noqa: F401 F403 diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py new file mode 100644 index 0000000000..0c920d56a3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py @@ -0,0 +1,469 @@ +from eth2spec.test.context import ( + spec_state_test, + with_electra_and_later, + always_bls, +) +from eth2spec.test.helpers.deposits import ( + prepare_pending_deposit, + run_pending_deposit_applying, +) +from eth2spec.test.helpers.state import next_epoch_via_block +from eth2spec.test.helpers.withdrawals import set_validator_fully_withdrawable + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_under_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement. + amount = spec.MIN_ACTIVATION_BALANCE - 1 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_over_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # just 1 over the limit, effective balance should be set MIN_ACTIVATION_BALANCE during processing + amount = spec.MIN_ACTIVATION_BALANCE + 1 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_eth1_withdrawal_credentials(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_under_max(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement. + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA - 1 + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_max(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + # effective balance will be exactly the same as balance. + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_over_max(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + # just 1 over the limit, effective balance should be set MAX_EFFECTIVE_BALANCE_ELECTRA during processing + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + 1 + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_non_versioned_withdrawal_credentials(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + b'\xFF' # Non specified withdrawal credentials version + + b'\x02' * 31 # Garabage bytes + ) + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_non_versioned_withdrawal_credentials_over_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + b'\xFF' # Non specified withdrawal credentials version + + b'\x02' * 31 # Garabage bytes + ) + # just 1 over the limit, effective balance should be set MIN_ACTIVATION_BALANCE during processing + amount = spec.MIN_ACTIVATION_BALANCE + 1 + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_correct_sig_but_forked_state(spec, state): + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + # deposits will always be valid, regardless of the current fork + state.fork.current_version = spec.Version('0x1234abcd') + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_incorrect_sig_new_deposit(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit(spec, validator_index, amount) + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=False) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__min_activation_balance(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE + amount + assert state.validators[validator_index].effective_balance == spec.MIN_ACTIVATION_BALANCE + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__min_activation_balance_compounding(spec, state): + validator_index = 0 + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].withdrawal_credentials = withdrawal_credentials + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE + amount + assert state.validators[validator_index].effective_balance == spec.MIN_ACTIVATION_BALANCE + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__max_effective_balance_compounding(spec, state): + validator_index = 0 + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b'\x00' * 11 # specified 0s + + b'\x59' * 20 # a 20-byte eth1 address + ) + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + state.validators[validator_index].withdrawal_credentials = withdrawal_credentials + state.validators[validator_index].effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == spec.MAX_EFFECTIVE_BALANCE_ELECTRA + amount + assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__less_effective_balance(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + initial_balance = spec.MIN_ACTIVATION_BALANCE - 1000 + initial_effective_balance = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.balances[validator_index] = initial_balance + state.validators[validator_index].effective_balance = initial_effective_balance + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == initial_balance + amount + # unchanged effective balance + assert state.validators[validator_index].effective_balance == initial_effective_balance + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__zero_balance(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + initial_balance = 0 + initial_effective_balance = 0 + state.balances[validator_index] = initial_balance + state.validators[validator_index].effective_balance = initial_effective_balance + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == initial_balance + amount + # unchanged effective balance + assert state.validators[validator_index].effective_balance == initial_effective_balance + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_incorrect_sig_top_up(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + # invalid signatures, in top-ups, are allowed! + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_incorrect_withdrawal_credentials_top_up(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(b"junk")[1:] + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + signed=True, + withdrawal_credentials=withdrawal_credentials + ) + + # inconsistent withdrawal credentials, in top-ups, are allowed! + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_key_validate_invalid_subgroup(spec, state): + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + + # All-zero pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception. + pubkey = b'\x00' * 48 + + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, pubkey=pubkey, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=False) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_key_validate_invalid_decompression(spec, state): + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + + # `deserialization_fails_infinity_with_true_b_flag` BLS G1 deserialization test case. + # This pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception. + pubkey_hex = 'c01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + pubkey = bytes.fromhex(pubkey_hex) + + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, pubkey=pubkey, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=False) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_ineffective_deposit_with_bad_fork_version(spec, state): + validator_index = len(state.validators) + fork_version = spec.Version('0xAaBbCcDd') + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=False) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_with_previous_fork_version(spec, state): + # Since deposits are valid across forks, the domain is always set with `GENESIS_FORK_VERSION`. + # It's an ineffective deposit because it fails at BLS sig verification. + # NOTE: it was effective in Altair. + assert state.fork.previous_version != state.fork.current_version + + validator_index = len(state.validators) + fork_version = state.fork.previous_version + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=False) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_ineffective_deposit_with_current_fork_version(spec, state): + validator_index = len(state.validators) + fork_version = state.fork.current_version + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=False) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_effective_deposit_with_genesis_fork_version(spec, state): + assert spec.config.GENESIS_FORK_VERSION not in (state.fork.previous_version, state.fork.current_version) + + validator_index = len(state.validators) + fork_version = spec.config.GENESIS_FORK_VERSION + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_success_top_up_to_withdrawn_validator(spec, state): + validator_index = 0 + + # Fully withdraw validator + set_validator_fully_withdrawable(spec, state, validator_index) + assert state.balances[validator_index] > 0 + next_epoch_via_block(spec, state) + assert state.balances[validator_index] == 0 + assert state.validators[validator_index].effective_balance > 0 + next_epoch_via_block(spec, state) + assert state.validators[validator_index].effective_balance == 0 + + # Make a top-up balance to validator + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == amount + assert state.validators[validator_index].effective_balance == 0 + + validator = state.validators[validator_index] + balance = state.balances[validator_index] + current_epoch = spec.get_current_epoch(state) + + assert spec.is_fully_withdrawable_validator(validator, balance, current_epoch) diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py new file mode 100644 index 0000000000..e1f2544020 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py @@ -0,0 +1,490 @@ +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.context import ( + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.deposits import prepare_pending_deposit +from eth2spec.test.helpers.state import ( + next_epoch_with_full_participation, + advance_finality_to, + set_full_participation, +) + + +def run_process_pending_deposits(spec, state): + yield from run_epoch_processing_with( + spec, state, 'process_pending_deposits') + + +def _ensure_enough_churn_to_process_deposits(spec, state): + state.deposit_balance_to_consume = sum(d.amount for d in state.pending_deposits) + + +def _prepare_eth1_bridge_deprecation(spec, state, eth1_bridge_flags): + new_pending_deposits = [] + validator_index_base = len(state.validators) + deposit_request_slot = spec.Slot(1) + for index, eth1_bridge in enumerate(eth1_bridge_flags): + validator_index = validator_index_base + index + slot = spec.GENESIS_SLOT if eth1_bridge else deposit_request_slot + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + signed=True, + slot=slot, + ) + new_pending_deposits.append(pending_deposit) + + # Eth1 bridge deposits instantly yield new validator records + if eth1_bridge: + spec.add_validator_to_registry( + state, + pending_deposit.pubkey, + pending_deposit.withdrawal_credentials, + spec.Gwei(0) + ) + state.eth1_deposit_index += 1 + + # Advance state to make pending deposits finalized + advance_finality_to(spec, state, spec.compute_epoch_at_slot(deposit_request_slot) + 1) + + # Add pending deposits + for pending_deposit in new_pending_deposits: + state.pending_deposits.append(pending_deposit) + + # Ensure there is enough churn to process them all + _ensure_enough_churn_to_process_deposits(spec, state) + + return state, new_pending_deposits + + +def _check_pending_deposits_induced_new_validators(spec, state, pre_validator_count, applied_pending_deposits): + assert pre_validator_count + len(applied_pending_deposits) == len(state.validators) + + eth1_bridge_deposits = [d for d in applied_pending_deposits if d.slot == spec.GENESIS_SLOT] + deposit_requests = [d for d in applied_pending_deposits if d.slot > spec.GENESIS_SLOT] + + # Eth1 bridge deposits should induce new validators in the first place + for index, deposit in enumerate(eth1_bridge_deposits): + validator_index = pre_validator_count + index + validator = state.validators[validator_index] + assert state.balances[validator_index] == deposit.amount + assert validator.pubkey == deposit.pubkey + # Effective balance is updated after pending deposits by process_effective_balance_updates + assert validator.effective_balance == spec.Gwei(0) + assert validator.withdrawal_credentials == deposit.withdrawal_credentials + + # then deposit requests go + for index, deposit in enumerate(deposit_requests): + validator_index = pre_validator_count + len(eth1_bridge_deposits) + index + validator = state.validators[validator_index] + assert state.balances[validator_index] == deposit.amount + assert validator.pubkey == deposit.pubkey + assert validator.withdrawal_credentials == deposit.withdrawal_credentials + # Validators induced from deposit requests get instant update of the EB + assert validator.effective_balance == deposit.amount + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_eth1_bridge_transition_pending(spec, state): + # There are pending Eth1 bridge deposits + # state.eth1_deposit_index < state.deposit_requests_start_index + pre_validator_count = len(state.validators) + state.eth1_data.deposit_count = len(state.validators) + 3 + state.deposit_requests_start_index = state.eth1_data.deposit_count + + state, new_pending_deposits = _prepare_eth1_bridge_deprecation(spec, state, [True, True, False]) + assert state.eth1_deposit_index < state.deposit_requests_start_index + + yield from run_process_pending_deposits(spec, state) + + # Eth1 bridge deposits were applied + _check_pending_deposits_induced_new_validators(spec, state, pre_validator_count, new_pending_deposits[:2]) + # deposit request was postponed and not processed + assert state.pending_deposits == new_pending_deposits[2:] + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_eth1_bridge_transition_not_applied(spec, state): + # There are pending Eth1 bridge deposits + # state.eth1_deposit_index < state.deposit_requests_start_index + pre_validator_count = len(state.validators) + state.eth1_data.deposit_count = len(state.validators) + 3 + state.deposit_requests_start_index = state.eth1_data.deposit_count + + state, new_pending_deposits = _prepare_eth1_bridge_deprecation(spec, state, [False, True, True]) + assert state.eth1_deposit_index < state.deposit_requests_start_index + + yield from run_process_pending_deposits(spec, state) + + # no pending deposit was processed, however Eth1 bridge deposits induced new validators + assert pre_validator_count + 2 == len(state.validators) + assert state.pending_deposits == new_pending_deposits + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_eth1_bridge_transition_complete(spec, state): + # There is no pending Eth1 bridge deposits + # state.eth1_deposit_index == state.deposit_requests_start_index + pre_validator_count = len(state.validators) + state.eth1_data.deposit_count = len(state.validators) + 2 + state.deposit_requests_start_index = state.eth1_data.deposit_count + + state, new_pending_deposits = _prepare_eth1_bridge_deprecation(spec, state, [True, False, True]) + assert state.eth1_deposit_index == state.deposit_requests_start_index + + yield from run_process_pending_deposits(spec, state) + + # all deposits were applied + assert state.pending_deposits == [] + _check_pending_deposits_induced_new_validators(spec, state, pre_validator_count, new_pending_deposits) + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_not_finalized(spec, state): + # complete eth1 bridge transition + state.deposit_requests_start_index = 0 + # advance state three epochs into the future + for _ in range(0, 3): + next_epoch_with_full_participation(spec, state) + # create pending deposits + pre_validator_count = len(state.validators) + for index in range(0, 2): + state.pending_deposits.append( + prepare_pending_deposit( + spec, + validator_index=pre_validator_count + index, + amount=spec.MIN_ACTIVATION_BALANCE, + signed=True, + slot=state.slot + index + ) + ) + new_pending_deposits = state.pending_deposits.copy() + + # finalize a slot before the slot of the first deposit + advance_finality_to(spec, state, spec.get_current_epoch(state) - 1) + + # process pending deposits + # the slot of the first deposit will be finalized before the call to process_pending_deposits + set_full_participation(spec, state) + _ensure_enough_churn_to_process_deposits(spec, state) + + yield from run_process_pending_deposits(spec, state) + + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + # second deposit was not processed as it hasn't been finalized + assert state.pending_deposits == new_pending_deposits[1:] + _check_pending_deposits_induced_new_validators(spec, state, pre_validator_count, new_pending_deposits[:1]) + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_limit_is_reached(spec, state): + # set pending deposits to the maximum + amount = spec.EFFECTIVE_BALANCE_INCREMENT * 1 + for i in range(spec.MAX_PENDING_DEPOSITS_PER_EPOCH + 2): + wc = state.validators[i].withdrawal_credentials + pd = prepare_pending_deposit(spec, i, amount, withdrawal_credentials=wc, signed=True) + state.pending_deposits.append(pd) + new_pending_deposits = state.pending_deposits.copy() + + # process pending deposits + pre_balances = state.balances.copy() + _ensure_enough_churn_to_process_deposits(spec, state) + + yield from run_process_pending_deposits(spec, state) + + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + # no deposits above limit were processed + assert state.pending_deposits == new_pending_deposits[spec.MAX_PENDING_DEPOSITS_PER_EPOCH:] + for i in range(spec.MAX_PENDING_DEPOSITS_PER_EPOCH): + assert state.balances[i] == pre_balances[i] + amount + for i in range(spec.MAX_PENDING_DEPOSITS_PER_EPOCH, spec.MAX_PENDING_DEPOSITS_PER_EPOCH + 2): + assert state.balances[i] == pre_balances[i] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_balance_equal_churn(spec, state): + index = 0 + amount = spec.get_activation_exit_churn_limit(state) + state.pending_deposits.append( + prepare_pending_deposit(spec, index, amount) + ) + pre_balance = state.balances[index] + + yield from run_process_pending_deposits(spec, state) + + assert state.balances[index] == pre_balance + amount + assert state.deposit_balance_to_consume == 0 + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_balance_above_churn(spec, state): + index = 0 + amount = spec.get_activation_exit_churn_limit(state) + 1 + state.pending_deposits.append( + prepare_pending_deposit(spec, index, amount) + ) + pre_balance = state.balances[index] + + yield from run_process_pending_deposits(spec, state) + + # deposit was above churn, balance hasn't changed + assert state.balances[index] == pre_balance + # deposit balance to consume is the full churn limit + wantedBalanceToConsume = spec.get_activation_exit_churn_limit(state) + assert state.deposit_balance_to_consume == wantedBalanceToConsume + # deposit is still in the queue + assert state.pending_deposits == [ + prepare_pending_deposit(spec, index, amount) + ] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_preexisting_churn(spec, state): + index = 0 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + 1 + state.deposit_balance_to_consume = 2 * amount + state.pending_deposits.append( + prepare_pending_deposit(spec, index, amount) + ) + pre_balance = state.balances[index] + + yield from run_process_pending_deposits(spec, state) + + # balance was deposited correctly + assert state.balances[index] == pre_balance + amount + # No leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + # queue emptied + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_pending_deposits_below_churn(spec, state): + amount = spec.EFFECTIVE_BALANCE_INCREMENT + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=0, amount=amount) + ) + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=1, amount=amount) + ) + pre_balances = state.balances.copy() + + yield from run_process_pending_deposits(spec, state) + + for i in [0, 1]: + assert state.balances[i] == pre_balances[i] + amount + # No leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_pending_deposits_above_churn(spec, state): + # set third deposit to be over the churn + amount = (spec.get_activation_exit_churn_limit(state) // 3) + 1 + for i in [0, 1, 2]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + pre_balances = state.balances.copy() + + yield from run_process_pending_deposits(spec, state) + + # First two deposits are processed, third is not because above churn + for i in [0, 1]: + assert state.balances[i] == pre_balances[i] + amount + assert state.balances[2] == pre_balances[2] + # Only first two subtract from the deposit balance to consume + assert ( + state.deposit_balance_to_consume + == spec.get_activation_exit_churn_limit(state) - 2 * amount + ) + # third deposit is still in the queue + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=2, amount=amount) + ] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_skipped_deposit_exiting_validator(spec, state): + index = 0 + amount = spec.MIN_ACTIVATION_BALANCE + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=index, amount=amount) + ) + pre_pending_deposits = state.pending_deposits.copy() + pre_balance = state.balances[index] + # Initiate the validator's exit + spec.initiate_validator_exit(state, index) + + yield from run_process_pending_deposits(spec, state) + + # Deposit is skipped because validator is exiting + assert state.balances[index] == pre_balance + # All deposits either processed or postponed + assert state.deposit_balance_to_consume == 0 + # The deposit is still in the queue + assert state.pending_deposits == pre_pending_deposits + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_skipped_deposits_exiting_validators(spec, state): + amount = spec.EFFECTIVE_BALANCE_INCREMENT + for i in [0, 1, 2]: + # Append pending deposit for validator i + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + + # Initiate the exit of validator i + spec.initiate_validator_exit(state, i) + pre_pending_deposits = state.pending_deposits.copy() + pre_balances = state.balances.copy() + + yield from run_process_pending_deposits(spec, state) + + # All deposits are postponed, no balance changes + assert state.balances == pre_balances + # All deposits are postponed, no leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + # All deposits still in the queue, in the same order + assert state.pending_deposits == pre_pending_deposits + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_pending_one_skipped(spec, state): + amount = spec.EFFECTIVE_BALANCE_INCREMENT + for i in [0, 1, 2]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + pre_balances = state.balances.copy() + # Initiate the second validator's exit + spec.initiate_validator_exit(state, 1) + + yield from run_process_pending_deposits(spec, state) + + # First and last deposit are processed, second is not because of exiting + for i in [0, 2]: + assert state.balances[i] == pre_balances[i] + amount + assert state.balances[1] == pre_balances[1] + # All deposits either processed or postponed + assert state.deposit_balance_to_consume == 0 + # second deposit is still in the queue + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=1, amount=amount) + ] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_mixture_of_skipped_and_above_churn(spec, state): + amount01 = spec.EFFECTIVE_BALANCE_INCREMENT + amount2 = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + # First two validators have small deposit, third validators a large one + for i in [0, 1]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount01) + ) + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=2, amount=amount2) + ) + pre_balances = state.balances.copy() + # Initiate the second validator's exit + spec.initiate_validator_exit(state, 1) + + yield from run_process_pending_deposits(spec, state) + + # First deposit is processed + assert state.balances[0] == pre_balances[0] + amount01 + # Second deposit is postponed, third is above churn + for i in [1, 2]: + assert state.balances[i] == pre_balances[i] + # First deposit consumes some deposit balance + # Deposit is not processed + wanted_balance = spec.get_activation_exit_churn_limit(state) - amount01 + assert state.deposit_balance_to_consume == wanted_balance + # second and third deposit still in the queue + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=2, amount=amount2), + prepare_pending_deposit(spec, validator_index=1, amount=amount01) + ] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_withdrawable_validator(spec, state): + index = 0 + amount = spec.MIN_ACTIVATION_BALANCE + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=index, amount=amount) + ) + pre_balance = state.balances[index] + # Initiate the validator's exit + spec.initiate_validator_exit(state, index) + # Set epoch to withdrawable epoch + 1 to allow processing of the deposit + withdrawable_epoch = state.validators[index].withdrawable_epoch + state.slot = spec.SLOTS_PER_EPOCH * (withdrawable_epoch + 1) + + yield from run_process_pending_deposits(spec, state) + + # Deposit is correctly processed + assert state.balances[index] == pre_balance + amount + # No leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_withdrawable_validator_not_churned(spec, state): + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + for i in [0, 1]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + pre_balances = state.balances.copy() + # Initiate the first validator's exit + spec.initiate_validator_exit(state, 0) + # Set epoch to withdrawable epoch + 1 to allow processing of the deposit + withdraw_epoch = state.validators[0].withdrawable_epoch + state.slot = spec.SLOTS_PER_EPOCH * (withdraw_epoch + 1) + # Don't use run_epoch_processing_with to avoid penalties being applied + yield 'pre', state + spec.process_pending_deposits(state) + yield 'post', state + # First deposit is processed though above churn limit + assert state.balances[0] == pre_balances[0] + amount + # Second deposit is not processed because above churn + assert state.balances[1] == pre_balances[1] + # Second deposit is not processed + # First deposit does not consume any. + wanted_limit = spec.get_activation_exit_churn_limit(state) + assert state.deposit_balance_to_consume == wanted_limit + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=1, amount=amount) + ] diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_balance_deposits.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_balance_deposits.py deleted file mode 100644 index c5789e8090..0000000000 --- a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_balance_deposits.py +++ /dev/null @@ -1,278 +0,0 @@ -from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with -from eth2spec.test.context import ( - spec_state_test, - with_electra_and_later, -) - - -def run_process_pending_balance_deposits(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_pending_balance_deposits') - - -@with_electra_and_later -@spec_state_test -def test_pending_deposit_min_activation_balance(spec, state): - index = 0 - amount = spec.MIN_ACTIVATION_BALANCE - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=index, amount=amount) - ) - pre_balance = state.balances[index] - - yield from run_process_pending_balance_deposits(spec, state) - - assert state.balances[index] == pre_balance + amount - # No leftover deposit balance to consume when there are no deposits left to process - assert state.deposit_balance_to_consume == 0 - assert state.pending_balance_deposits == [] - - -@with_electra_and_later -@spec_state_test -def test_pending_deposit_balance_equal_churn(spec, state): - index = 0 - amount = spec.get_activation_exit_churn_limit(state) - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=index, amount=amount) - ) - pre_balance = state.balances[index] - - yield from run_process_pending_balance_deposits(spec, state) - - assert state.balances[index] == pre_balance + amount - assert state.deposit_balance_to_consume == 0 - assert state.pending_balance_deposits == [] - - -@with_electra_and_later -@spec_state_test -def test_pending_deposit_balance_above_churn(spec, state): - index = 0 - amount = spec.get_activation_exit_churn_limit(state) + 1 - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=index, amount=amount) - ) - pre_balance = state.balances[index] - - yield from run_process_pending_balance_deposits(spec, state) - - # deposit was above churn, balance hasn't changed - assert state.balances[index] == pre_balance - # deposit balance to consume is the full churn limit - assert state.deposit_balance_to_consume == spec.get_activation_exit_churn_limit( - state - ) - # deposit is still in the queue - assert state.pending_balance_deposits == [ - spec.PendingBalanceDeposit(index=index, amount=amount) - ] - - -@with_electra_and_later -@spec_state_test -def test_pending_deposit_preexisting_churn(spec, state): - index = 0 - amount = 10**9 + 1 - state.deposit_balance_to_consume = 2 * amount - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=index, amount=amount) - ) - pre_balance = state.balances[index] - - yield from run_process_pending_balance_deposits(spec, state) - - # balance was deposited correctly - assert state.balances[index] == pre_balance + amount - # No leftover deposit balance to consume when there are no deposits left to process - assert state.deposit_balance_to_consume == 0 - # queue emptied - assert state.pending_balance_deposits == [] - - -@with_electra_and_later -@spec_state_test -def test_multiple_pending_deposits_below_churn(spec, state): - amount = 10**9 - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=0, amount=amount) - ) - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=1, amount=amount) - ) - pre_balances = state.balances.copy() - - yield from run_process_pending_balance_deposits(spec, state) - - for i in [0, 1]: - assert state.balances[i] == pre_balances[i] + amount - # No leftover deposit balance to consume when there are no deposits left to process - assert state.deposit_balance_to_consume == 0 - assert state.pending_balance_deposits == [] - - -@with_electra_and_later -@spec_state_test -def test_multiple_pending_deposits_above_churn(spec, state): - # set third deposit to be over the churn - amount = (spec.get_activation_exit_churn_limit(state) // 3) + 1 - for i in [0, 1, 2]: - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=i, amount=amount) - ) - pre_balances = state.balances.copy() - - yield from run_process_pending_balance_deposits(spec, state) - - # First two deposits are processed, third is not because above churn - for i in [0, 1]: - assert state.balances[i] == pre_balances[i] + amount - assert state.balances[2] == pre_balances[2] - # Only first two subtract from the deposit balance to consume - assert ( - state.deposit_balance_to_consume - == spec.get_activation_exit_churn_limit(state) - 2 * amount - ) - # third deposit is still in the queue - assert state.pending_balance_deposits == [ - spec.PendingBalanceDeposit(index=2, amount=amount) - ] - - -@with_electra_and_later -@spec_state_test -def test_skipped_deposit_exiting_validator(spec, state): - index = 0 - amount = spec.MIN_ACTIVATION_BALANCE - state.pending_balance_deposits.append(spec.PendingBalanceDeposit(index=index, amount=amount)) - pre_pending_balance_deposits = state.pending_balance_deposits.copy() - pre_balance = state.balances[index] - # Initiate the validator's exit - spec.initiate_validator_exit(state, index) - - yield from run_process_pending_balance_deposits(spec, state) - - # Deposit is skipped because validator is exiting - assert state.balances[index] == pre_balance - # All deposits either processed or postponed, no leftover deposit balance to consume - assert state.deposit_balance_to_consume == 0 - # The deposit is still in the queue - assert state.pending_balance_deposits == pre_pending_balance_deposits - - -@with_electra_and_later -@spec_state_test -def test_multiple_skipped_deposits_exiting_validators(spec, state): - amount = spec.EFFECTIVE_BALANCE_INCREMENT - for i in [0, 1, 2]: - # Append pending deposit for validator i - state.pending_balance_deposits.append(spec.PendingBalanceDeposit(index=i, amount=amount)) - - # Initiate the exit of validator i - spec.initiate_validator_exit(state, i) - pre_pending_balance_deposits = state.pending_balance_deposits.copy() - pre_balances = state.balances.copy() - - yield from run_process_pending_balance_deposits(spec, state) - - # All deposits are postponed, no balance changes - assert state.balances == pre_balances - # All deposits are postponed, no leftover deposit balance to consume - assert state.deposit_balance_to_consume == 0 - # All deposits still in the queue, in the same order - assert state.pending_balance_deposits == pre_pending_balance_deposits - - -@with_electra_and_later -@spec_state_test -def test_multiple_pending_one_skipped(spec, state): - amount = spec.EFFECTIVE_BALANCE_INCREMENT - for i in [0, 1, 2]: - state.pending_balance_deposits.append(spec.PendingBalanceDeposit(index=i, amount=amount)) - pre_balances = state.balances.copy() - # Initiate the second validator's exit - spec.initiate_validator_exit(state, 1) - - yield from run_process_pending_balance_deposits(spec, state) - - # First and last deposit are processed, second is not because of exiting - for i in [0, 2]: - assert state.balances[i] == pre_balances[i] + amount - assert state.balances[1] == pre_balances[1] - # All deposits either processed or postponed, no leftover deposit balance to consume - assert state.deposit_balance_to_consume == 0 - # second deposit is still in the queue - assert state.pending_balance_deposits == [spec.PendingBalanceDeposit(index=1, amount=amount)] - - -@with_electra_and_later -@spec_state_test -def test_mixture_of_skipped_and_above_churn(spec, state): - amount01 = spec.EFFECTIVE_BALANCE_INCREMENT - amount2 = spec.MAX_EFFECTIVE_BALANCE_ELECTRA - # First two validators have small deposit, third validators a large one - for i in [0, 1]: - state.pending_balance_deposits.append(spec.PendingBalanceDeposit(index=i, amount=amount01)) - state.pending_balance_deposits.append(spec.PendingBalanceDeposit(index=2, amount=amount2)) - pre_balances = state.balances.copy() - # Initiate the second validator's exit - spec.initiate_validator_exit(state, 1) - - yield from run_process_pending_balance_deposits(spec, state) - - # First deposit is processed - assert state.balances[0] == pre_balances[0] + amount01 - # Second deposit is postponed, third is above churn - for i in [1, 2]: - assert state.balances[i] == pre_balances[i] - # First deposit consumes some deposit balance - # Deposit balance to consume is not reset because third deposit is not processed - assert state.deposit_balance_to_consume == spec.get_activation_exit_churn_limit(state) - amount01 - # second and third deposit still in the queue, but second is appended at the end - assert state.pending_balance_deposits == [spec.PendingBalanceDeposit(index=2, amount=amount2), - spec.PendingBalanceDeposit(index=1, amount=amount01)] - - -@with_electra_and_later -@spec_state_test -def test_processing_deposit_of_withdrawable_validator(spec, state): - index = 0 - amount = spec.MIN_ACTIVATION_BALANCE - state.pending_balance_deposits.append(spec.PendingBalanceDeposit(index=index, amount=amount)) - pre_balance = state.balances[index] - # Initiate the validator's exit - spec.initiate_validator_exit(state, index) - # Set epoch to withdrawable epoch + 1 to allow processing of the deposit - state.slot = spec.SLOTS_PER_EPOCH * (state.validators[index].withdrawable_epoch + 1) - - yield from run_process_pending_balance_deposits(spec, state) - - # Deposit is correctly processed - assert state.balances[index] == pre_balance + amount - # No leftover deposit balance to consume when there are no deposits left to process - assert state.deposit_balance_to_consume == 0 - assert state.pending_balance_deposits == [] - - -@with_electra_and_later -@spec_state_test -def test_processing_deposit_of_withdrawable_validator_does_not_get_churned(spec, state): - amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA - for i in [0, 1]: - state.pending_balance_deposits.append(spec.PendingBalanceDeposit(index=i, amount=amount)) - pre_balances = state.balances.copy() - # Initiate the first validator's exit - spec.initiate_validator_exit(state, 0) - # Set epoch to withdrawable epoch + 1 to allow processing of the deposit - state.slot = spec.SLOTS_PER_EPOCH * (state.validators[0].withdrawable_epoch + 1) - # Don't use run_epoch_processing_with to avoid penalties being applied - yield 'pre', state - spec.process_pending_balance_deposits(state) - yield 'post', state - # First deposit is processed though above churn limit, because validator is withdrawable - assert state.balances[0] == pre_balances[0] + amount - # Second deposit is not processed because above churn - assert state.balances[1] == pre_balances[1] - # Second deposit is not processed, so there's leftover deposit balance to consume. - # First deposit does not consume any. - assert state.deposit_balance_to_consume == spec.get_activation_exit_churn_limit(state) - assert state.pending_balance_deposits == [spec.PendingBalanceDeposit(index=1, amount=amount)] diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py index 61714562d9..322224b78e 100644 --- a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py @@ -245,7 +245,7 @@ def test_pending_consolidation_compounding_creds(spec, state): # Pending balance deposit to the target is not created, # because the target already has compounding credentials - assert len(state.pending_balance_deposits) == 0 + assert len(state.pending_deposits) == 0 @with_electra_and_later @@ -257,15 +257,21 @@ def test_pending_consolidation_with_pending_deposit(spec, state): # initiate source exit spec.initiate_validator_exit(state, source_index) # set withdrawable_epoch to exit_epoch + 1 - state.validators[source_index].withdrawable_epoch = state.validators[source_index].exit_epoch + spec.Epoch(1) + source = state.validators[source_index] + source.withdrawable_epoch = source.exit_epoch + spec.Epoch(1) # append pending consolidation state.pending_consolidations.append( spec.PendingConsolidation(source_index=source_index, target_index=target_index) ) # append pending deposit - state.pending_balance_deposits.append( - spec.PendingBalanceDeposit(index=source_index, amount=spec.MIN_ACTIVATION_BALANCE) + pending_deposit = spec.PendingDeposit( + pubkey=source.pubkey, + withdrawal_credentials=source.withdrawal_credentials, + amount=spec.MIN_ACTIVATION_BALANCE, + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, ) + state.pending_deposits.append(pending_deposit) # Set the source and the target withdrawal credential to compounding state.validators[source_index].withdrawal_credentials = ( spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 @@ -275,7 +281,7 @@ def test_pending_consolidation_with_pending_deposit(spec, state): ) # Advance to withdrawable_epoch - 1 with full participation - target_epoch = state.validators[source_index].withdrawable_epoch - spec.Epoch(1) + target_epoch = source.withdrawable_epoch - spec.Epoch(1) while spec.get_current_epoch(state) < target_epoch: next_epoch_with_full_participation(spec, state) @@ -293,8 +299,6 @@ def test_pending_consolidation_with_pending_deposit(spec, state): assert state.balances[source_index] == 0 assert state.pending_consolidations == [] - # Pending balance deposit to the source was not processed. + # Pending deposit to the source was not processed. # It should only be processed in the next epoch transition - assert len(state.pending_balance_deposits) == 1 - assert state.pending_balance_deposits[0] == spec.PendingBalanceDeposit( - index=source_index, amount=spec.MIN_ACTIVATION_BALANCE) + assert state.pending_deposits == [pending_deposit] diff --git a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py index 852521a32b..e569be35e3 100644 --- a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py +++ b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py @@ -91,7 +91,7 @@ def test_fork_pre_activation(spec, phases, state): state.validators[0].activation_epoch = spec.FAR_FUTURE_EPOCH post_state = yield from run_fork_test(post_spec, state) - assert len(post_state.pending_balance_deposits) > 0 + assert len(post_state.pending_deposits) > 0 @with_phases(phases=[DENEB], other_phases=[ELECTRA]) @@ -105,4 +105,4 @@ def test_fork_has_compounding_withdrawal_credential(spec, phases, state): validator.withdrawal_credentials = post_spec.COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] post_state = yield from run_fork_test(post_spec, state) - assert len(post_state.pending_balance_deposits) > 0 + assert len(post_state.pending_deposits) > 0 diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py index e846543f23..9749c89ffd 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py @@ -16,7 +16,7 @@ ) from eth2spec.test.helpers.keys import privkeys, pubkeys from eth2spec.test.helpers.state import ( - state_transition_and_sign_block + state_transition_and_sign_block, ) @@ -30,6 +30,10 @@ def run_deposit_transition_block(spec, state, block, top_up_keys=[], valid=True) """ yield 'pre', state + pre_pending_deposits_len = len(state.pending_deposits) + pre_validators_len = len(state.validators) + + # Include deposits into a block signed_block = state_transition_and_sign_block(spec, state, block, not valid) yield 'blocks', [signed_block] @@ -37,12 +41,39 @@ def run_deposit_transition_block(spec, state, block, top_up_keys=[], valid=True) # Check that deposits are applied if valid: - expected_pubkeys = [d.data.pubkey for d in block.body.deposits] - deposit_requests = block.body.execution_requests.deposits - expected_pubkeys = expected_pubkeys + [d.pubkey for d in deposit_requests if (d.pubkey not in top_up_keys)] - actual_pubkeys = [v.pubkey for v in state.validators[len(state.validators) - len(expected_pubkeys):]] - - assert actual_pubkeys == expected_pubkeys + # Check that deposits are applied + for i, deposit in enumerate(block.body.deposits): + # Validator is created with 0 balance + validator = state.validators[pre_validators_len + i] + assert validator.pubkey == deposit.data.pubkey + assert validator.withdrawal_credentials == deposit.data.withdrawal_credentials + assert validator.effective_balance == spec.Gwei(0) + assert state.balances[pre_validators_len + i] == spec.Gwei(0) + + # The corresponding pending deposit is created + pending_deposit = state.pending_deposits[pre_pending_deposits_len + i] + assert pending_deposit.pubkey == deposit.data.pubkey + assert pending_deposit.withdrawal_credentials == deposit.data.withdrawal_credentials + assert pending_deposit.amount == deposit.data.amount + assert pending_deposit.signature == deposit.data.signature + assert pending_deposit.slot == spec.GENESIS_SLOT + + # Assert that no unexpected validators were created + assert len(state.validators) == pre_validators_len + len(block.body.deposits) + + # Check that deposit requests are processed + for i, deposit_request in enumerate(block.body.execution_requests.deposits): + # The corresponding pending deposit is created + pending_deposit = state.pending_deposits[pre_pending_deposits_len + len(block.body.deposits) + i] + assert pending_deposit.pubkey == deposit_request.pubkey + assert pending_deposit.withdrawal_credentials == deposit_request.withdrawal_credentials + assert pending_deposit.amount == deposit_request.amount + assert pending_deposit.signature == deposit_request.signature + assert pending_deposit.slot == signed_block.message.slot + + # Assert that no unexpected pending deposits were created + assert len(state.pending_deposits) == pre_pending_deposits_len + len( + block.body.deposits) + len(block.body.execution_requests.deposits) def prepare_state_and_block(spec, @@ -222,12 +253,12 @@ def test_deposit_transition__deposit_and_top_up_same_block(spec, state): block.body.execution_requests.deposits[0].pubkey = top_up_keys[0] block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload, state) - pre_pending_deposits = len(state.pending_balance_deposits) + pre_pending_deposits = len(state.pending_deposits) yield from run_deposit_transition_block(spec, state, block, top_up_keys=top_up_keys) # Check the top up - assert len(state.pending_balance_deposits) == pre_pending_deposits + 2 - assert state.pending_balance_deposits[pre_pending_deposits].amount == block.body.deposits[0].data.amount + assert len(state.pending_deposits) == pre_pending_deposits + 2 + assert state.pending_deposits[pre_pending_deposits].amount == block.body.deposits[0].data.amount amount_from_deposit = block.body.execution_requests.deposits[0].amount - assert state.pending_balance_deposits[pre_pending_deposits + 1].amount == amount_from_deposit + assert state.pending_deposits[pre_pending_deposits + 1].amount == amount_from_deposit diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index c8aa30313c..bd0d670491 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -4,6 +4,7 @@ from eth2spec.test.helpers.forks import is_post_altair, is_post_electra from eth2spec.test.helpers.keys import pubkeys, privkeys from eth2spec.test.helpers.state import get_balance +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_to from eth2spec.utils import bls from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof from eth2spec.utils.ssz.ssz_impl import hash_tree_root @@ -23,23 +24,26 @@ def mock_deposit(spec, state, index): assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) -def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=False): +def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, fork_version=None, signed=False): deposit_data = spec.DepositData( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, ) if signed: - sign_deposit_data(spec, deposit_data, privkey) + sign_deposit_data(spec, deposit_data, privkey, fork_version) return deposit_data -def sign_deposit_data(spec, deposit_data, privkey): +def sign_deposit_data(spec, deposit_data, privkey, fork_version=None): deposit_message = spec.DepositMessage( pubkey=deposit_data.pubkey, withdrawal_credentials=deposit_data.withdrawal_credentials, amount=deposit_data.amount) - domain = spec.compute_domain(spec.DOMAIN_DEPOSIT) + if fork_version is not None: + domain = spec.compute_domain(domain_type=spec.DOMAIN_DEPOSIT, fork_version=fork_version) + else: + domain = spec.compute_domain(spec.DOMAIN_DEPOSIT) signing_root = spec.compute_signing_root(deposit_message, domain) deposit_data.signature = bls.Sign(privkey, signing_root) @@ -171,34 +175,48 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, return deposit -def build_deposit_request(spec, - index, - pubkey, - privkey, - amount, - withdrawal_credentials, - signed): +def prepare_deposit_request(spec, validator_index, amount, + index=None, + pubkey=None, + privkey=None, + withdrawal_credentials=None, + signed=False): + """ + Create a deposit request for the given validator, depositing the given amount. + """ + if index is None: + index = validator_index + + if pubkey is None: + pubkey = pubkeys[validator_index] + + if privkey is None: + privkey = privkeys[validator_index] + + # insecurely use pubkey as withdrawal key if no credentials provided + if withdrawal_credentials is None: + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] + deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=signed) return spec.DepositRequest( pubkey=deposit_data.pubkey, withdrawal_credentials=deposit_data.withdrawal_credentials, amount=deposit_data.amount, signature=deposit_data.signature, - index=index) + index=index + ) -def prepare_deposit_request(spec, validator_index, amount, - index=None, +def prepare_pending_deposit(spec, validator_index, amount, pubkey=None, privkey=None, withdrawal_credentials=None, - signed=False): + fork_version=None, + signed=False, + slot=None): """ - Create a deposit request for the given validator, depositing the given amount. + Create a pending deposit for the given validator, depositing the given amount. """ - if index is None: - index = validator_index - if pubkey is None: pubkey = pubkeys[validator_index] @@ -209,14 +227,24 @@ def prepare_deposit_request(spec, validator_index, amount, if withdrawal_credentials is None: withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] - return build_deposit_request( - spec, - index, - pubkey, - privkey, - amount, - withdrawal_credentials, - signed, + # use GENESIS_SLOT which is always finalized if no slot provided + if slot is None: + slot = spec.GENESIS_SLOT + + deposit_data = build_deposit_data(spec, + pubkey, + privkey, + amount, + withdrawal_credentials, + fork_version, + signed) + + return spec.PendingDeposit( + pubkey=deposit_data.pubkey, + amount=deposit_data.amount, + withdrawal_credentials=deposit_data.withdrawal_credentials, + signature=deposit_data.signature, + slot=slot, ) # @@ -243,7 +271,7 @@ def run_deposit_processing(spec, state, deposit, validator_index, valid=True, ef pre_effective_balance = state.validators[validator_index].effective_balance if is_post_electra(spec): - pre_pending_deposits = len(state.pending_balance_deposits) + pre_pending_deposits = len(state.pending_deposits) yield 'pre', state yield 'deposit', deposit @@ -285,9 +313,13 @@ def run_deposit_processing(spec, state, deposit, validator_index, valid=True, ef assert get_balance(state, validator_index) == pre_balance assert state.validators[validator_index].effective_balance == pre_effective_balance # new correct balance deposit queued up - assert len(state.pending_balance_deposits) == pre_pending_deposits + 1 - assert state.pending_balance_deposits[pre_pending_deposits].amount == deposit.data.amount - assert state.pending_balance_deposits[pre_pending_deposits].index == validator_index + assert len(state.pending_deposits) == pre_pending_deposits + 1 + assert state.pending_deposits[pre_pending_deposits].pubkey == deposit.data.pubkey + assert state.pending_deposits[ + pre_pending_deposits].withdrawal_credentials == deposit.data.withdrawal_credentials + assert state.pending_deposits[pre_pending_deposits].amount == deposit.data.amount + assert state.pending_deposits[pre_pending_deposits].signature == deposit.data.signature + assert state.pending_deposits[pre_pending_deposits].slot == spec.GENESIS_SLOT assert state.eth1_deposit_index == state.eth1_data.deposit_count @@ -320,14 +352,21 @@ def run_deposit_processing_with_specific_fork_version( yield from run_deposit_processing(spec, state, deposit, validator_index, valid=valid, effective=effective) -def run_deposit_request_processing(spec, state, deposit_request, validator_index, valid=True, effective=True): +def run_deposit_request_processing( + spec, + state, + deposit_request, + validator_index, + effective=True): + """ Run ``process_deposit_request``, yielding: - pre-state ('pre') - deposit_request ('deposit_request') - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` """ + assert is_post_electra(spec) + pre_validator_count = len(state.validators) pre_balance = 0 is_top_up = False @@ -337,72 +376,91 @@ def run_deposit_request_processing(spec, state, deposit_request, validator_index pre_balance = get_balance(state, validator_index) pre_effective_balance = state.validators[validator_index].effective_balance - pre_pending_deposits = len(state.pending_balance_deposits) - yield 'pre', state yield 'deposit_request', deposit_request - if not valid: - expect_assertion_error(lambda: spec.process_deposit_request(state, deposit_request)) - yield 'post', None - return - spec.process_deposit_request(state, deposit_request) yield 'post', state - if not effective or not bls.KeyValidate(deposit_request.pubkey): - assert len(state.validators) == pre_validator_count - assert len(state.balances) == pre_validator_count - if is_top_up: - assert get_balance(state, validator_index) == pre_balance - else: + # New validator is only created after the pending_deposits processing + assert len(state.validators) == pre_validator_count + assert len(state.balances) == pre_validator_count + + if is_top_up: + assert state.validators[validator_index].effective_balance == pre_effective_balance + assert state.balances[validator_index] == pre_balance + + pending_deposit = spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=state.slot, + ) + + assert state.pending_deposits == [pending_deposit] + + +def run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=True): + """ + Enqueue ``pending_deposit`` and run epoch processing with ``process_pending_deposits``, yielding: + - pre-state ('pre') + - post-state ('post'). + """ + assert is_post_electra(spec) + + # ensure the transition from eth1 bridge is complete + state.deposit_requests_start_index = state.eth1_deposit_index + + # ensure there is enough churn to apply the deposit + if pending_deposit.amount > spec.get_activation_exit_churn_limit(state): + state.deposit_balance_to_consume = pending_deposit.amount - spec.get_activation_exit_churn_limit(state) + + # append pending deposit + state.pending_deposits.append(pending_deposit) + + # run to the very beginning of the epoch processing to avoid + # any updates to the validator registry (e.g. ejections) + run_epoch_processing_to(spec, state, "process_justification_and_finalization") + + pre_validator_count = len(state.validators) + pre_balance = 0 + pre_effective_balance = 0 + is_top_up = False + # is a top-up + if validator_index < pre_validator_count: + is_top_up = True + pre_balance = get_balance(state, validator_index) + pre_effective_balance = state.validators[validator_index].effective_balance + + yield 'pre', state + + spec.process_pending_deposits(state) + + yield 'post', state + + if effective: if is_top_up: - # Top-ups do not change effective balance - assert state.validators[validator_index].effective_balance == pre_effective_balance + # Top-ups don't add validators assert len(state.validators) == pre_validator_count assert len(state.balances) == pre_validator_count + # Top-ups do not change effective balance + assert state.validators[validator_index].effective_balance == pre_effective_balance else: - # new validator + # new validator is added assert len(state.validators) == pre_validator_count + 1 assert len(state.balances) == pre_validator_count + 1 + # effective balance is set correctly + max_effective_balace = spec.get_max_effective_balance(state.validators[validator_index]) + effective_balance = min(max_effective_balace, pending_deposit.amount) + effective_balance -= effective_balance % spec.EFFECTIVE_BALANCE_INCREMENT + assert state.validators[validator_index].effective_balance == effective_balance + assert get_balance(state, validator_index) == pre_balance + pending_deposit.amount + else: + assert len(state.validators) == pre_validator_count + assert len(state.balances) == pre_validator_count + if is_top_up: + assert get_balance(state, validator_index) == pre_balance - assert len(state.pending_balance_deposits) == pre_pending_deposits + 1 - assert state.pending_balance_deposits[pre_pending_deposits].amount == deposit_request.amount - assert state.pending_balance_deposits[pre_pending_deposits].index == validator_index - - -def run_deposit_request_processing_with_specific_fork_version( - spec, - state, - fork_version, - valid=True, - effective=True): - validator_index = len(state.validators) - amount = spec.MAX_EFFECTIVE_BALANCE - - pubkey = pubkeys[validator_index] - privkey = privkeys[validator_index] - withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] - - deposit_message = spec.DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) - domain = spec.compute_domain(domain_type=spec.DOMAIN_DEPOSIT, fork_version=fork_version) - deposit_data = spec.DepositData( - pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, - signature=bls.Sign(privkey, spec.compute_signing_root(deposit_message, domain)) - ) - deposit_request = spec.DepositRequest( - pubkey=deposit_data.pubkey, - withdrawal_credentials=deposit_data.withdrawal_credentials, - amount=deposit_data.amount, - signature=deposit_data.signature, - index=validator_index) - - yield from run_deposit_request_processing( - spec, - state, - deposit_request, - validator_index, - valid=valid, - effective=effective - ) + assert len(state.pending_deposits) == 0 diff --git a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py index 5cd8e7de65..66d68c8ff4 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py @@ -22,7 +22,7 @@ def get_process_calls(spec): 'charge_confirmed_header_fees', # sharding 'reset_pending_headers', # sharding 'process_eth1_data_reset', - 'process_pending_balance_deposits', # electra + 'process_pending_deposits', # electra 'process_pending_consolidations', # electra 'process_effective_balance_updates', 'process_slashings_reset', diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index bc7d891855..bd4e5d3bf3 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -166,7 +166,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold): state.earliest_exit_epoch = spec.GENESIS_EPOCH state.consolidation_balance_to_consume = 0 state.earliest_consolidation_epoch = 0 - state.pending_balance_deposits = [] + state.pending_deposits = [] state.pending_partial_withdrawals = [] state.pending_consolidations = [] diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 07e7bfb478..72fca0b14f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -179,3 +179,8 @@ def has_active_balance_differential(spec, state): def get_validator_index_by_pubkey(state, pubkey): index = next((i for i, validator in enumerate(state.validators) if validator.pubkey == pubkey), None) return index + + +def advance_finality_to(spec, state, epoch): + while state.finalized_checkpoint.epoch < epoch: + next_epoch_with_full_participation(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py index 3fffcb0369..50209e2e7c 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py @@ -41,6 +41,20 @@ def run_test_effective_balance_hysteresis(spec, state, with_compounding_credenti (min, min + (hys_inc * div * 2), min + (2 * inc), "exact two step balance increment"), (min, min + (hys_inc * div * 2) + 1, min + (2 * inc), "over two steps, round down"), ] + + if with_compounding_credentials: + min = spec.MIN_ACTIVATION_BALANCE + cases = cases + [ + (min, min + (hys_inc * up), min, "bigger balance, but not high enough"), + (min, min + (hys_inc * up) + 1, min + inc, "bigger balance, high enough, but small step"), + (min, min + (hys_inc * div * 2) - 1, min + inc, "bigger balance, high enough, close to double step"), + (min, min + (hys_inc * div * 2), min + (2 * inc), "exact two step balance increment"), + (min, min + (hys_inc * div * 2) + 1, min + (2 * inc), "over two steps, round down"), + (min, min * 2 + 1, min * 2, "top up or consolidation doubling the balance"), + (min, min * 2 - 1, min * 2 - spec.EFFECTIVE_BALANCE_INCREMENT, + "top up or consolidation almost doubling the balance"), + ] + current_epoch = spec.get_current_epoch(state) for i, (pre_eff, bal, _, _) in enumerate(cases): assert spec.is_active_validator(state.validators[i], current_epoch) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 148cb939d5..56d3721b86 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -750,7 +750,7 @@ def test_deposit_in_block(spec, state): yield 'post', state if is_post_electra(spec): - balance = state.pending_balance_deposits[0].amount + balance = state.pending_deposits[0].amount else: balance = get_balance(state, validator_index) @@ -821,7 +821,7 @@ def test_deposit_top_up(spec, state): balance = get_balance(state, validator_index) if is_post_electra(spec): - balance += state.pending_balance_deposits[0].amount + balance += state.pending_deposits[0].amount assert balance == ( validator_pre_balance + amount + sync_committee_reward - sync_committee_penalty diff --git a/tests/formats/epoch_processing/README.md b/tests/formats/epoch_processing/README.md index 2951767f2c..0f6642141a 100644 --- a/tests/formats/epoch_processing/README.md +++ b/tests/formats/epoch_processing/README.md @@ -32,19 +32,21 @@ The provided pre-state is already transitioned to just before the specific sub-t Sub-transitions: -- `justification_and_finalization` -- `inactivity_updates` (Altair) -- `rewards_and_penalties` -- `registry_updates` -- `slashings` -- `eth1_data_reset` -- `effective_balance_updates` -- `slashings_reset` -- `randao_mixes_reset` -- `historical_roots_update` (Phase0, Altair, Bellatrix only) -- `historical_summaries_update` (Capella) -- `participation_record_updates` (Phase 0 only) -- `participation_flag_updates` (Altair) -- `sync_committee_updates` (Altair) +- `eth1_data_reset` (>=Phase0) +- `historical_roots_update` (>=Phase0,<=Bellatrix) +- `justification_and_finalization` (>=Phase0) +- `participation_record_updates` (==Phase0) +- `randao_mixes_reset` (>=Phase0) +- `registry_updates` (>=Phase0) +- `rewards_and_penalties` (>=Phase0) +- `slashings_reset` (>=Phase0) +- `slashings` (>=Phase0) +- `inactivity_updates` (>=Altair) +- `participation_flag_updates` (>=Altair) +- `sync_committee_updates` (>=Altair) +- `historical_summaries_update` (>=Capella) +- `effective_balance_updates` (>=Electra) +- `pending_consolidations` (>=Electra) +- `pending_deposits` (>=Electra) The resulting state should match the expected `post` state. diff --git a/tests/generators/epoch_processing/main.py b/tests/generators/epoch_processing/main.py index 3796f76a97..1de3e84e3d 100644 --- a/tests/generators/epoch_processing/main.py +++ b/tests/generators/epoch_processing/main.py @@ -37,11 +37,15 @@ ]} deneb_mods = combine_mods(_new_deneb_mods, capella_mods) - _new_electra_mods = {key: 'eth2spec.test.electra.epoch_processing.test_process_' + key for key in [ + _new_electra_mods_1 = {key: 'eth2spec.test.electra.epoch_processing.test_process_' + key for key in [ 'effective_balance_updates', - 'pending_balance_deposits', 'pending_consolidations', ]} + # This is a trick to allow tests be split into multiple files and use the same test format. + _new_electra_mods_2 = {key: 'eth2spec.test.electra.epoch_processing.' + key for key in [ + 'pending_deposits', + ]} + _new_electra_mods = {**_new_electra_mods_1, **_new_electra_mods_2} electra_mods = combine_mods(_new_electra_mods, deneb_mods) # TODO Custody Game testgen is disabled for now