handle top-ups to exiting/exited validators
This commit is contained in:
parent
f968d62459
commit
69ee35d4e3
|
@ -798,12 +798,27 @@ def process_pending_balance_deposits(state: BeaconState) -> None:
|
|||
available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(state)
|
||||
processed_amount = 0
|
||||
next_deposit_index = 0
|
||||
deposits_to_postpone = []
|
||||
|
||||
for deposit in state.pending_balance_deposits:
|
||||
if processed_amount + deposit.amount > available_for_processing:
|
||||
break
|
||||
increase_balance(state, deposit.index, deposit.amount)
|
||||
processed_amount += deposit.amount
|
||||
validator = state.validators[deposit.index]
|
||||
# Validator is exiting, postpone the deposit until after withdrawable epoch
|
||||
if validator.exit_epoch < FAR_FUTURE_EPOCH:
|
||||
if get_current_epoch(state) <= validator.withdrawable_epoch:
|
||||
deposits_to_postpone.append(deposit)
|
||||
# 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
|
||||
else:
|
||||
# Deposit does not fit in the churn, no more deposit processing in this epoch.
|
||||
if processed_amount + deposit.amount > available_for_processing:
|
||||
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
|
||||
# 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:]
|
||||
|
@ -812,6 +827,8 @@ def process_pending_balance_deposits(state: BeaconState) -> None:
|
|||
state.deposit_balance_to_consume = Gwei(0)
|
||||
else:
|
||||
state.deposit_balance_to_consume = available_for_processing - processed_amount
|
||||
|
||||
state.pending_balance_deposits += deposits_to_postpone
|
||||
```
|
||||
|
||||
#### New `process_pending_consolidations`
|
||||
|
|
|
@ -132,3 +132,133 @@ def test_multiple_pending_deposits_above_churn(spec, state):
|
|||
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_epoch_processing_with(spec, state, 'process_pending_balance_deposits')
|
||||
# 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_epoch_processing_with(spec, state, 'process_pending_balance_deposits')
|
||||
# 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_epoch_processing_with(spec, state, 'process_pending_balance_deposits')
|
||||
# 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_epoch_processing_with(spec, state, 'process_pending_balance_deposits')
|
||||
# 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_epoch_processing_with(spec, state, 'process_pending_balance_deposits')
|
||||
# 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)]
|
||||
|
|
Loading…
Reference in New Issue