Skip to content

Commit f5c0f2d

Browse files
authored
Fix bug in electra withdrawals processing (#4281)
1 parent f65a82d commit f5c0f2d

File tree

2 files changed

+82
-5
lines changed

2 files changed

+82
-5
lines changed

specs/electra/beacon-chain.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,10 +1114,12 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal],
11141114

11151115
validator = state.validators[withdrawal.validator_index]
11161116
has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
1117-
has_excess_balance = state.balances[withdrawal.validator_index] > MIN_ACTIVATION_BALANCE
1117+
total_withdrawn = sum(w.amount for w in withdrawals if w.validator_index == withdrawal.validator_index)
1118+
balance = state.balances[withdrawal.validator_index] - total_withdrawn
1119+
has_excess_balance = balance > MIN_ACTIVATION_BALANCE
11181120
if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance:
11191121
withdrawable_balance = min(
1120-
state.balances[withdrawal.validator_index] - MIN_ACTIVATION_BALANCE,
1122+
balance - MIN_ACTIVATION_BALANCE,
11211123
withdrawal.amount)
11221124
withdrawals.append(Withdrawal(
11231125
index=withdrawal_index,
@@ -1134,9 +1136,8 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal],
11341136
for _ in range(bound):
11351137
validator = state.validators[validator_index]
11361138
# [Modified in Electra:EIP7251]
1137-
partially_withdrawn_balance = sum(
1138-
withdrawal.amount for withdrawal in withdrawals if withdrawal.validator_index == validator_index)
1139-
balance = state.balances[validator_index] - partially_withdrawn_balance
1139+
total_withdrawn = sum(w.amount for w in withdrawals if w.validator_index == validator_index)
1140+
balance = state.balances[validator_index] - total_withdrawn
11401141
if is_fully_withdrawable_validator(validator, balance, epoch):
11411142
withdrawals.append(Withdrawal(
11421143
index=withdrawal_index,

tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,3 +748,79 @@ def test_partially_withdrawable_validator_compounding_min_minus_one(spec, state)
748748
partial_withdrawals_indices=[],
749749
)
750750
assert state.pending_partial_withdrawals == []
751+
752+
753+
@with_electra_and_later
754+
@spec_state_test
755+
def test_pending_withdrawals_two_partial_withdrawals_same_validator_1(spec, state):
756+
validator_index = 0
757+
758+
# Initialize a compounding validator
759+
set_compounding_withdrawal_credential_with_balance(
760+
spec,
761+
state,
762+
validator_index,
763+
effective_balance=spec.Gwei(32_000_000_000),
764+
balance=spec.Gwei(33_000_000_000),
765+
)
766+
767+
# Add two pending withdrawals of 1 ETH each to the queue
768+
withdrawal = spec.PendingPartialWithdrawal(
769+
validator_index=validator_index,
770+
amount=spec.Gwei(1_000_000_000),
771+
withdrawable_epoch=spec.get_current_epoch(state),
772+
)
773+
state.pending_partial_withdrawals = [withdrawal, withdrawal]
774+
775+
# Ensure our two pending withdrawals are there
776+
assert len(state.pending_partial_withdrawals) == 2
777+
778+
yield from run_withdrawals_processing(
779+
spec,
780+
state,
781+
build_empty_execution_payload(spec, state),
782+
num_expected_withdrawals=1,
783+
)
784+
785+
# Ensure our two pending withdrawals were processed
786+
assert state.pending_partial_withdrawals == []
787+
# Ensure the validator's balance is the minimum
788+
assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE
789+
790+
791+
@with_electra_and_later
792+
@spec_state_test
793+
def test_pending_withdrawals_two_partial_withdrawals_same_validator_2(spec, state):
794+
validator_index = 0
795+
796+
# Initialize a compounding validator
797+
set_compounding_withdrawal_credential_with_balance(
798+
spec,
799+
state,
800+
validator_index,
801+
effective_balance=spec.Gwei(2015_000_000_000),
802+
balance=spec.Gwei(2015_000_000_000),
803+
)
804+
805+
# Add two pending withdrawals of 1008 ETH each to the queue
806+
withdrawal = spec.PendingPartialWithdrawal(
807+
validator_index=validator_index,
808+
amount=spec.Gwei(1008_000_000_000),
809+
withdrawable_epoch=spec.get_current_epoch(state),
810+
)
811+
state.pending_partial_withdrawals = [withdrawal, withdrawal]
812+
813+
# Ensure our two pending withdrawals are there
814+
assert len(state.pending_partial_withdrawals) == 2
815+
816+
yield from run_withdrawals_processing(
817+
spec,
818+
state,
819+
build_empty_execution_payload(spec, state),
820+
num_expected_withdrawals=2,
821+
)
822+
823+
# Ensure our two pending withdrawals were processed
824+
assert state.pending_partial_withdrawals == []
825+
# Ensure the validator's balance is the minimum
826+
assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE

0 commit comments

Comments
 (0)