diff --git a/beacon-chain/core/blocks/withdrawals.go b/beacon-chain/core/blocks/withdrawals.go index 5b234d1614a8..e2f202ce9081 100644 --- a/beacon-chain/core/blocks/withdrawals.go +++ b/beacon-chain/core/blocks/withdrawals.go @@ -120,32 +120,35 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si // // Spec pseudocode definition: // -// def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: +// def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: +// expected_withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) # [Modified in Electra:EIP7251] // -// expected_withdrawals = get_expected_withdrawals(state) -// assert len(payload.withdrawals) == len(expected_withdrawals) +// assert len(payload.withdrawals) == len(expected_withdrawals) // -// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals): -// assert withdrawal == expected_withdrawal -// decrease_balance(state, withdrawal.validator_index, withdrawal.amount) +// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals): +// assert withdrawal == expected_withdrawal +// decrease_balance(state, withdrawal.validator_index, withdrawal.amount) // -// # Update the next withdrawal index if this block contained withdrawals -// if len(expected_withdrawals) != 0: -// latest_withdrawal = expected_withdrawals[-1] -// state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) +// # Update pending partial withdrawals [New in Electra:EIP7251] +// state.pending_partial_withdrawals = state.pending_partial_withdrawals[partial_withdrawals_count:] // -// # Update the next validator index to start the next withdrawal sweep -// if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: -// # Next sweep starts after the latest withdrawal's validator index -// next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators)) -// state.next_withdrawal_validator_index = next_validator_index -// else: -// # Advance sweep by the max length of the sweep if there was not a full set of withdrawals -// next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP -// next_validator_index = ValidatorIndex(next_index % len(state.validators)) -// state.next_withdrawal_validator_index = next_validator_index +// # Update the next withdrawal index if this block contained withdrawals +// if len(expected_withdrawals) != 0: +// latest_withdrawal = expected_withdrawals[-1] +// state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) +// +// # Update the next validator index to start the next withdrawal sweep +// if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: +// # Next sweep starts after the latest withdrawal's validator index +// next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators)) +// state.next_withdrawal_validator_index = next_validator_index +// else: +// # Advance sweep by the max length of the sweep if there was not a full set of withdrawals +// next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP +// next_validator_index = ValidatorIndex(next_index % len(state.validators)) +// state.next_withdrawal_validator_index = next_validator_index func ProcessWithdrawals(st state.BeaconState, executionData interfaces.ExecutionData) (state.BeaconState, error) { - expectedWithdrawals, _, err := st.ExpectedWithdrawals() + expectedWithdrawals, partialWithdrawalsCount, err := st.ExpectedWithdrawals() if err != nil { return nil, errors.Wrap(err, "could not get expected withdrawals") } @@ -162,6 +165,11 @@ func ProcessWithdrawals(st state.BeaconState, executionData interfaces.Execution if err != nil { return nil, errors.Wrap(err, "could not get withdrawals") } + + if len(wds) != len(expectedWithdrawals) { + return nil, fmt.Errorf("execution payload header has %d withdrawals when %d were expected", len(wds), len(expectedWithdrawals)) + } + wdRoot, err = ssz.WithdrawalSliceRoot(wds, fieldparams.MaxWithdrawalsPerPayload) if err != nil { return nil, errors.Wrap(err, "could not get withdrawals root") @@ -182,6 +190,13 @@ func ProcessWithdrawals(st state.BeaconState, executionData interfaces.Execution return nil, errors.Wrap(err, "could not decrease balance") } } + + if st.Version() >= version.Electra { + if err := st.DequeuePartialWithdrawals(partialWithdrawalsCount); err != nil { + return nil, fmt.Errorf("unable to dequeue partial withdrawals from state: %w", err) + } + } + if len(expectedWithdrawals) > 0 { if err := st.SetNextWithdrawalIndex(expectedWithdrawals[len(expectedWithdrawals)-1].Index + 1); err != nil { return nil, errors.Wrap(err, "could not set next withdrawal index") diff --git a/beacon-chain/core/blocks/withdrawals_test.go b/beacon-chain/core/blocks/withdrawals_test.go index 6b0f6caf660b..7d7dc5dc08f6 100644 --- a/beacon-chain/core/blocks/withdrawals_test.go +++ b/beacon-chain/core/blocks/withdrawals_test.go @@ -12,6 +12,7 @@ import ( fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/crypto/bls" "github.com/prysmaticlabs/prysm/v5/crypto/bls/common" @@ -19,6 +20,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/encoding/ssz" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/time/slots" ) @@ -675,6 +677,7 @@ func TestProcessWithdrawals(t *testing.T) { FullWithdrawalIndices []primitives.ValidatorIndex PendingPartialWithdrawalIndices []primitives.ValidatorIndex Withdrawals []*enginev1.Withdrawal + PendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal // Electra } type control struct { NextWithdrawalValidatorIndex primitives.ValidatorIndex @@ -772,7 +775,7 @@ func TestProcessWithdrawals(t *testing.T) { }, { Args: args{ - Name: "Less than max sweep at end", + Name: "less than max sweep at end", NextWithdrawalIndex: 22, NextWithdrawalValidatorIndex: 4, FullWithdrawalIndices: []primitives.ValidatorIndex{80, 81, 82, 83}, @@ -789,7 +792,7 @@ func TestProcessWithdrawals(t *testing.T) { }, { Args: args{ - Name: "Less than max sweep and beginning", + Name: "less than max sweep and beginning", NextWithdrawalIndex: 22, NextWithdrawalValidatorIndex: 4, FullWithdrawalIndices: []primitives.ValidatorIndex{4, 5, 6}, @@ -846,6 +849,36 @@ func TestProcessWithdrawals(t *testing.T) { }, }, }, + { + Args: args{ + Name: "success many withdrawals with pending partial withdrawals in state", + NextWithdrawalIndex: 22, + NextWithdrawalValidatorIndex: 88, + FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28}, + PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{2, 1, 89, 15}, + Withdrawals: []*enginev1.Withdrawal{ + PendingPartialWithdrawal(89, 22), PendingPartialWithdrawal(1, 23), PendingPartialWithdrawal(2, 24), + fullWithdrawal(7, 25), PendingPartialWithdrawal(15, 26), fullWithdrawal(19, 27), + fullWithdrawal(28, 28), + }, + PendingPartialWithdrawals: []*ethpb.PendingPartialWithdrawal{ + { + Index: 11, + Amount: withdrawalAmount(11) - maxEffectiveBalance, + }, + }, + }, + Control: control{ + NextWithdrawalValidatorIndex: 40, + NextWithdrawalIndex: 29, + Balances: map[uint64]uint64{ + 7: 0, 19: 0, 28: 0, + 2: maxEffectiveBalance, 1: maxEffectiveBalance, 89: maxEffectiveBalance, + 15: maxEffectiveBalance, + }, + }, + }, + { Args: args{ Name: "success more than max fully withdrawals", @@ -1011,65 +1044,97 @@ func TestProcessWithdrawals(t *testing.T) { } } - prepareValidators := func(st *ethpb.BeaconStateCapella, arguments args) (state.BeaconState, error) { + prepareValidators := func(st state.BeaconState, arguments args) error { validators := make([]*ethpb.Validator, numValidators) - st.Balances = make([]uint64, numValidators) + if err := st.SetBalances(make([]uint64, numValidators)); err != nil { + return err + } for i := range validators { v := ðpb.Validator{} v.EffectiveBalance = maxEffectiveBalance v.WithdrawableEpoch = epochInFuture v.WithdrawalCredentials = make([]byte, 32) v.WithdrawalCredentials[31] = byte(i) - st.Balances[i] = v.EffectiveBalance - uint64(rand.Intn(1000)) + if err := st.UpdateBalancesAtIndex(primitives.ValidatorIndex(i), v.EffectiveBalance-uint64(rand.Intn(1000))); err != nil { + return err + } validators[i] = v } for _, idx := range arguments.FullWithdrawalIndices { if idx != notWithdrawableIndex { validators[idx].WithdrawableEpoch = epochInPast } - st.Balances[idx] = withdrawalAmount(idx) + if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil { + return err + } validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte } for _, idx := range arguments.PendingPartialWithdrawalIndices { validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - st.Balances[idx] = withdrawalAmount(idx) + if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil { + return err + } } - st.Validators = validators - return state_native.InitializeFromProtoCapella(st) + return st.SetValidators(validators) } for _, test := range tests { t.Run(test.Args.Name, func(t *testing.T) { - saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep - params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = maxSweep - if test.Args.Withdrawals == nil { - test.Args.Withdrawals = make([]*enginev1.Withdrawal, 0) - } - if test.Args.FullWithdrawalIndices == nil { - test.Args.FullWithdrawalIndices = make([]primitives.ValidatorIndex, 0) - } - if test.Args.PendingPartialWithdrawalIndices == nil { - test.Args.PendingPartialWithdrawalIndices = make([]primitives.ValidatorIndex, 0) - } - slot, err := slots.EpochStart(currentEpoch) - require.NoError(t, err) - spb := ðpb.BeaconStateCapella{ - Slot: slot, - NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex, - NextWithdrawalIndex: test.Args.NextWithdrawalIndex, - } - st, err := prepareValidators(spb, test.Args) - require.NoError(t, err) - p, err := consensusblocks.WrappedExecutionPayloadCapella(&enginev1.ExecutionPayloadCapella{Withdrawals: test.Args.Withdrawals}) - require.NoError(t, err) - post, err := blocks.ProcessWithdrawals(st, p) - if test.Control.ExpectedError { - require.NotNil(t, err) - } else { - require.NoError(t, err) - checkPostState(t, test.Control, post) + for _, fork := range []int{version.Capella, version.Electra} { + t.Run(version.String(fork), func(t *testing.T) { + saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep + params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = maxSweep + if test.Args.Withdrawals == nil { + test.Args.Withdrawals = make([]*enginev1.Withdrawal, 0) + } + if test.Args.FullWithdrawalIndices == nil { + test.Args.FullWithdrawalIndices = make([]primitives.ValidatorIndex, 0) + } + if test.Args.PendingPartialWithdrawalIndices == nil { + test.Args.PendingPartialWithdrawalIndices = make([]primitives.ValidatorIndex, 0) + } + slot, err := slots.EpochStart(currentEpoch) + require.NoError(t, err) + var st state.BeaconState + var p interfaces.ExecutionData + switch fork { + case version.Capella: + spb := ðpb.BeaconStateCapella{ + Slot: slot, + NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex, + NextWithdrawalIndex: test.Args.NextWithdrawalIndex, + } + st, err = state_native.InitializeFromProtoUnsafeCapella(spb) + require.NoError(t, err) + p, err = consensusblocks.WrappedExecutionPayloadCapella(&enginev1.ExecutionPayloadCapella{Withdrawals: test.Args.Withdrawals}) + require.NoError(t, err) + case version.Electra: + spb := ðpb.BeaconStateElectra{ + Slot: slot, + NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex, + NextWithdrawalIndex: test.Args.NextWithdrawalIndex, + PendingPartialWithdrawals: test.Args.PendingPartialWithdrawals, + } + st, err = state_native.InitializeFromProtoUnsafeElectra(spb) + require.NoError(t, err) + p, err = consensusblocks.WrappedExecutionPayloadElectra(&enginev1.ExecutionPayloadElectra{Withdrawals: test.Args.Withdrawals}) + require.NoError(t, err) + default: + t.Fatalf("Add a beacon state setup for version %s", version.String(fork)) + } + err = prepareValidators(st, test.Args) + require.NoError(t, err) + post, err := blocks.ProcessWithdrawals(st, p) + if test.Control.ExpectedError { + require.NotNil(t, err) + } else { + require.NoError(t, err) + checkPostState(t, test.Control, post) + } + params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved + + }) } - params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved }) } } diff --git a/beacon-chain/core/electra/withdrawals.go b/beacon-chain/core/electra/withdrawals.go index c1f2bd5fdce0..13ccbc08f204 100644 --- a/beacon-chain/core/electra/withdrawals.go +++ b/beacon-chain/core/electra/withdrawals.go @@ -26,66 +26,67 @@ import ( // // def process_withdrawal_request( // -// state: BeaconState, -// withdrawal_request: WithdrawalRequest +// state: BeaconState, +// withdrawal_request: WithdrawalRequest // -// ) -> None: -// amount = execution_layer_withdrawal_request.amount -// is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT +// ) -> None: // -// # If partial withdrawal queue is full, only full exits are processed -// if len(state.pending_partial_withdrawals) == PENDING_PARTIAL_WITHDRAWALS_LIMIT and not is_full_exit_request: -// return +// amount = withdrawal_request.amount +// is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT // -// validator_pubkeys = [v.pubkey for v in state.validators] -// # Verify pubkey exists -// request_pubkey = execution_layer_withdrawal_request.validator_pubkey -// if request_pubkey not in validator_pubkeys: -// return -// index = ValidatorIndex(validator_pubkeys.index(request_pubkey)) -// validator = state.validators[index] +// # If partial withdrawal queue is full, only full exits are processed +// if len(state.pending_partial_withdrawals) == PENDING_PARTIAL_WITHDRAWALS_LIMIT and not is_full_exit_request: +// return // -// # Verify withdrawal credentials -// has_correct_credential = has_execution_withdrawal_credential(validator) -// is_correct_source_address = ( -// validator.withdrawal_credentials[12:] == execution_layer_withdrawal_request.source_address -// ) -// if not (has_correct_credential and is_correct_source_address): -// return -// # Verify the validator is active -// if not is_active_validator(validator, get_current_epoch(state)): -// return -// # Verify exit has not been initiated -// if validator.exit_epoch != FAR_FUTURE_EPOCH: -// return -// # Verify the validator has been active long enough -// if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD: -// return +// validator_pubkeys = [v.pubkey for v in state.validators] +// # Verify pubkey exists +// request_pubkey = withdrawal_request.validator_pubkey +// if request_pubkey not in validator_pubkeys: +// return +// index = ValidatorIndex(validator_pubkeys.index(request_pubkey)) +// validator = state.validators[index] // -// pending_balance_to_withdraw = get_pending_balance_to_withdraw(state, index) +// # Verify withdrawal credentials +// has_correct_credential = has_execution_withdrawal_credential(validator) +// is_correct_source_address = ( +// validator.withdrawal_credentials[12:] == withdrawal_request.source_address +// ) +// if not (has_correct_credential and is_correct_source_address): +// return +// # Verify the validator is active +// if not is_active_validator(validator, get_current_epoch(state)): +// return +// # Verify exit has not been initiated +// if validator.exit_epoch != FAR_FUTURE_EPOCH: +// return +// # Verify the validator has been active long enough +// if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD: +// return // -// if is_full_exit_request: -// # Only exit validator if it has no pending withdrawals in the queue -// if pending_balance_to_withdraw == 0: -// initiate_validator_exit(state, index) -// return +// pending_balance_to_withdraw = get_pending_balance_to_withdraw(state, index) // -// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE -// has_excess_balance = state.balances[index] > MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw +// if is_full_exit_request: +// # Only exit validator if it has no pending withdrawals in the queue +// if pending_balance_to_withdraw == 0: +// initiate_validator_exit(state, index) +// return // -// # Only allow partial withdrawals with compounding withdrawal credentials -// if has_compounding_withdrawal_credential(validator) and has_sufficient_effective_balance and has_excess_balance: -// to_withdraw = min( -// state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw, -// amount -// ) -// exit_queue_epoch = compute_exit_epoch_and_update_churn(state, to_withdraw) -// withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) -// state.pending_partial_withdrawals.append(PendingPartialWithdrawal( -// index=index, -// amount=to_withdraw, -// withdrawable_epoch=withdrawable_epoch, -// )) +// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE +// has_excess_balance = state.balances[index] > MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw +// +// # Only allow partial withdrawals with compounding withdrawal credentials +// if has_compounding_withdrawal_credential(validator) and has_sufficient_effective_balance and has_excess_balance: +// to_withdraw = min( +// state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw, +// amount +// ) +// exit_queue_epoch = compute_exit_epoch_and_update_churn(state, to_withdraw) +// withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) +// state.pending_partial_withdrawals.append(PendingPartialWithdrawal( +// index=index, +// amount=to_withdraw, +// withdrawable_epoch=withdrawable_epoch, +// )) func ProcessWithdrawalRequests(ctx context.Context, st state.BeaconState, wrs []*enginev1.WithdrawalRequest) (state.BeaconState, error) { ctx, span := trace.StartSpan(ctx, "electra.ProcessWithdrawalRequests") defer span.End() diff --git a/beacon-chain/core/helpers/BUILD.bazel b/beacon-chain/core/helpers/BUILD.bazel index 4e2a79941552..c33980c260f3 100644 --- a/beacon-chain/core/helpers/BUILD.bazel +++ b/beacon-chain/core/helpers/BUILD.bazel @@ -79,6 +79,7 @@ go_test( "//crypto/hash:go_default_library", "//encoding/bytesutil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", diff --git a/beacon-chain/core/helpers/validators.go b/beacon-chain/core/helpers/validators.go index d213c136f197..d896fe2de7f5 100644 --- a/beacon-chain/core/helpers/validators.go +++ b/beacon-chain/core/helpers/validators.go @@ -584,13 +584,13 @@ func IsSameWithdrawalCredentials(a, b *ethpb.Validator) bool { // and validator.withdrawable_epoch <= epoch // and balance > 0 // ) -func IsFullyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch) bool { +func IsFullyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch, fork int) bool { if val == nil || balance <= 0 { return false } // Electra / EIP-7251 logic - if epoch >= params.BeaconConfig().ElectraForkEpoch { + if fork >= version.Electra { return HasExecutionWithdrawalCredentials(val) && val.WithdrawableEpoch <= epoch } @@ -600,12 +600,12 @@ func IsFullyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch pr // IsPartiallyWithdrawableValidator returns whether the validator is able to perform a // partial withdrawal. This function assumes that the caller has a lock on the state. // This method conditionally calls the fork appropriate implementation based on the epoch argument. -func IsPartiallyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch) bool { +func IsPartiallyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch primitives.Epoch, fork int) bool { if val == nil { return false } - if epoch < params.BeaconConfig().ElectraForkEpoch { + if fork < version.Electra { return isPartiallyWithdrawableValidatorCapella(val, balance, epoch) } diff --git a/beacon-chain/core/helpers/validators_test.go b/beacon-chain/core/helpers/validators_test.go index c0ab33d395f1..3d10c711db43 100644 --- a/beacon-chain/core/helpers/validators_test.go +++ b/beacon-chain/core/helpers/validators_test.go @@ -16,6 +16,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/crypto/hash" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" ) @@ -970,6 +971,7 @@ func TestIsFullyWithdrawableValidator(t *testing.T) { validator *ethpb.Validator balance uint64 epoch primitives.Epoch + fork int want bool }{ { @@ -1027,13 +1029,14 @@ func TestIsFullyWithdrawableValidator(t *testing.T) { }, balance: params.BeaconConfig().MaxEffectiveBalance, epoch: params.BeaconConfig().ElectraForkEpoch, + fork: version.Electra, want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, helpers.IsFullyWithdrawableValidator(tt.validator, tt.balance, tt.epoch)) + assert.Equal(t, tt.want, helpers.IsFullyWithdrawableValidator(tt.validator, tt.balance, tt.epoch, tt.fork)) }) } } @@ -1044,6 +1047,7 @@ func TestIsPartiallyWithdrawableValidator(t *testing.T) { validator *ethpb.Validator balance uint64 epoch primitives.Epoch + fork int want bool }{ { @@ -1091,6 +1095,7 @@ func TestIsPartiallyWithdrawableValidator(t *testing.T) { }, balance: params.BeaconConfig().MinActivationBalance * 2, epoch: params.BeaconConfig().ElectraForkEpoch, + fork: version.Electra, want: true, }, { @@ -1101,13 +1106,14 @@ func TestIsPartiallyWithdrawableValidator(t *testing.T) { }, balance: params.BeaconConfig().MaxEffectiveBalanceElectra * 2, epoch: params.BeaconConfig().ElectraForkEpoch, + fork: version.Electra, want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, helpers.IsPartiallyWithdrawableValidator(tt.validator, tt.balance, tt.epoch)) + assert.Equal(t, tt.want, helpers.IsPartiallyWithdrawableValidator(tt.validator, tt.balance, tt.epoch, tt.fork)) }) } } diff --git a/beacon-chain/state/state-native/getters_withdrawal.go b/beacon-chain/state/state-native/getters_withdrawal.go index 2950900140ce..116363c772d4 100644 --- a/beacon-chain/state/state-native/getters_withdrawal.go +++ b/beacon-chain/state/state-native/getters_withdrawal.go @@ -154,7 +154,7 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err if err != nil { return nil, 0, errors.Wrapf(err, "could not retrieve balance at index %d", validatorIndex) } - if helpers.IsFullyWithdrawableValidator(val, balance, epoch) { + if helpers.IsFullyWithdrawableValidator(val, balance, epoch, b.version) { withdrawals = append(withdrawals, &enginev1.Withdrawal{ Index: withdrawalIndex, ValidatorIndex: validatorIndex, @@ -162,7 +162,7 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err Amount: balance, }) withdrawalIndex++ - } else if helpers.IsPartiallyWithdrawableValidator(val, balance, epoch) { + } else if helpers.IsPartiallyWithdrawableValidator(val, balance, epoch, b.version) { withdrawals = append(withdrawals, &enginev1.Withdrawal{ Index: withdrawalIndex, ValidatorIndex: validatorIndex,