Compare commits

...

1 Commits

Author SHA1 Message Date
james-prysm
96e7ef8767 withdrawal change and test
(cherry picked from commit 6e350dbde5a1e9290e76a678318f51ce52f20fba)
2025-04-15 10:25:24 -05:00
2 changed files with 95 additions and 52 deletions

View File

@@ -48,60 +48,62 @@ func (b *BeaconState) NextWithdrawalValidatorIndex() (primitives.ValidatorIndex,
//
// Spec definition:
//
// def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]:
// epoch = get_current_epoch(state)
// withdrawal_index = state.next_withdrawal_index
// validator_index = state.next_withdrawal_validator_index
// withdrawals: List[Withdrawal] = []
// processed_partial_withdrawals_count = 0
// def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]:
// epoch = get_current_epoch(state)
// withdrawal_index = state.next_withdrawal_index
// validator_index = state.next_withdrawal_validator_index
// withdrawals: List[Withdrawal] = []
// processed_partial_withdrawals_count = 0
//
// # [New in Electra:EIP7251] Consume pending partial withdrawals
// for withdrawal in state.pending_partial_withdrawals:
// if withdrawal.withdrawable_epoch > epoch or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP:
// break
// # [New in Electra:EIP7251] Consume pending partial withdrawals
// for withdrawal in state.pending_partial_withdrawals:
// if withdrawal.withdrawable_epoch > epoch or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP:
// break
//
// validator = state.validators[withdrawal.index]
// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
// has_excess_balance = state.balances[withdrawal.index] > MIN_ACTIVATION_BALANCE
// if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance:
// withdrawable_balance = min(state.balances[withdrawal.index] - MIN_ACTIVATION_BALANCE, withdrawal.amount)
// withdrawals.append(Withdrawal(
// index=withdrawal_index,
// validator_index=withdrawal.index,
// address=ExecutionAddress(validator.withdrawal_credentials[12:]),
// amount=withdrawable_balance,
// ))
// withdrawal_index += WithdrawalIndex(1)
// validator = state.validators[withdrawal.index]
// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
// total_withdrawn = sum(w.amount for w in withdrawals if w.validator_index == withdrawal.validator_index)
// balance = state.balances[withdrawal.validator_index] - total_withdrawn
// has_excess_balance = balance > MIN_ACTIVATION_BALANCE
// if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance:
// withdrawable_balance = min(balance - MIN_ACTIVATION_BALANCE, withdrawal.amount)
// withdrawals.append(Withdrawal(
// index=withdrawal_index,
// validator_index=withdrawal.index,
// address=ExecutionAddress(validator.withdrawal_credentials[12:]),
// amount=withdrawable_balance,
// ))
// withdrawal_index += WithdrawalIndex(1)
//
// processed_partial_withdrawals_count += 1
// processed_partial_withdrawals_count += 1
//
// # Sweep for remaining.
// bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
// for _ in range(bound):
// validator = state.validators[validator_index]
// # [Modified in Electra:EIP7251]
// partially_withdrawn_balance = sum(withdrawal.amount for withdrawal in withdrawals if withdrawal.validator_index == validator_index)
// balance = state.balances[validator_index] - partially_withdrawn_balance
// if is_fully_withdrawable_validator(validator, balance, epoch):
// withdrawals.append(Withdrawal(
// index=withdrawal_index,
// validator_index=validator_index,
// address=ExecutionAddress(validator.withdrawal_credentials[12:]),
// amount=balance,
// ))
// withdrawal_index += WithdrawalIndex(1)
// elif is_partially_withdrawable_validator(validator, balance):
// withdrawals.append(Withdrawal(
// index=withdrawal_index,
// validator_index=validator_index,
// address=ExecutionAddress(validator.withdrawal_credentials[12:]),
// amount=balance - get_max_effective_balance(validator), # [Modified in Electra:EIP7251]
// ))
// withdrawal_index += WithdrawalIndex(1)
// if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
// break
// validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
// return withdrawals, processed_partial_withdrawals_count
// # Sweep for remaining.
// bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
// for _ in range(bound):
// validator = state.validators[validator_index]
// # [Modified in Electra:EIP7251]
// partially_withdrawn_balance = sum(withdrawal.amount for withdrawal in withdrawals if withdrawal.validator_index == validator_index)
// balance = state.balances[validator_index] - partially_withdrawn_balance
// if is_fully_withdrawable_validator(validator, balance, epoch):
// withdrawals.append(Withdrawal(
// index=withdrawal_index,
// validator_index=validator_index,
// address=ExecutionAddress(validator.withdrawal_credentials[12:]),
// amount=balance,
// ))
// withdrawal_index += WithdrawalIndex(1)
// elif is_partially_withdrawable_validator(validator, balance):
// withdrawals.append(Withdrawal(
// index=withdrawal_index,
// validator_index=validator_index,
// address=ExecutionAddress(validator.withdrawal_credentials[12:]),
// amount=balance - get_max_effective_balance(validator), # [Modified in Electra:EIP7251]
// ))
// withdrawal_index += WithdrawalIndex(1)
// if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
// break
// validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
// return withdrawals, processed_partial_withdrawals_count
func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, error) {
if b.version < version.Capella {
return nil, 0, errNotSupported("ExpectedWithdrawals", b.version)
@@ -132,9 +134,16 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
return nil, 0, fmt.Errorf("could not retrieve balance at index %d: %w", w.Index, err)
}
hasSufficientEffectiveBalance := v.EffectiveBalance() >= params.BeaconConfig().MinActivationBalance
hasExcessBalance := vBal > params.BeaconConfig().MinActivationBalance
var totalWithdrawn uint64
for _, wi := range withdrawals {
if wi.ValidatorIndex == w.Index {
totalWithdrawn += wi.Amount
}
}
balance := vBal - totalWithdrawn
hasExcessBalance := balance > params.BeaconConfig().MinActivationBalance
if v.ExitEpoch() == params.BeaconConfig().FarFutureEpoch && hasSufficientEffectiveBalance && hasExcessBalance {
amount := min(vBal-params.BeaconConfig().MinActivationBalance, w.Amount)
amount := min(balance-params.BeaconConfig().MinActivationBalance, w.Amount)
withdrawals = append(withdrawals, &enginev1.Withdrawal{
Index: withdrawalIndex,
ValidatorIndex: w.Index,

View File

@@ -416,3 +416,37 @@ func TestExpectedWithdrawals(t *testing.T) {
require.DeepEqual(t, withdrawalFull, expected[1])
})
}
func TestExpectedWithdrawals_underflow_electra(t *testing.T) {
s, err := state_native.InitializeFromProtoUnsafeElectra(&ethpb.BeaconStateElectra{})
require.NoError(t, err)
vals := make([]*ethpb.Validator, 1)
balances := make([]uint64, 1)
balances[0] = 2015_000_000_000 //Validator A begins leaking ETH due to inactivity, and over time, its balance decreases to 2,015 ETH
val := &ethpb.Validator{
WithdrawalCredentials: make([]byte, 32),
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra,
WithdrawableEpoch: primitives.Epoch(0),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
val.WithdrawalCredentials[0] = params.BeaconConfig().CompoundingWithdrawalPrefixByte
val.WithdrawalCredentials[31] = byte(0)
vals[0] = val
require.NoError(t, s.SetValidators(vals))
require.NoError(t, s.SetBalances(balances))
require.NoError(t, s.AppendPendingPartialWithdrawal(&ethpb.PendingPartialWithdrawal{
Amount: 1008_000_000_000,
WithdrawableEpoch: primitives.Epoch(0),
}))
require.NoError(t, s.AppendPendingPartialWithdrawal(&ethpb.PendingPartialWithdrawal{
Amount: 1008_000_000_000,
WithdrawableEpoch: primitives.Epoch(0),
}))
expected, _, err := s.ExpectedWithdrawals()
require.NoError(t, err)
require.Equal(t, 3, len(expected)) // is a fully withdrawable validator
require.Equal(t, uint64(1008_000_000_000), expected[0].Amount)
require.Equal(t, uint64(975_000_000_000), expected[1].Amount)
require.Equal(t, uint64(32_000_000_000), expected[2].Amount)
}