Skip to content

Commit 0eda70c

Browse files
committed
Switch to compounding when consolidating with source==target
1 parent adc5edd commit 0eda70c

File tree

3 files changed

+83
-97
lines changed

3 files changed

+83
-97
lines changed

specs/electra/beacon-chain.md

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
- [Modified `get_next_sync_committee_indices`](#modified-get_next_sync_committee_indices)
6262
- [Beacon state mutators](#beacon-state-mutators)
6363
- [Modified `initiate_validator_exit`](#modified-initiate_validator_exit)
64-
- [New `switch_to_compounding_validator`](#new-switch_to_compounding_validator)
6564
- [New `queue_excess_active_balance`](#new-queue_excess_active_balance)
6665
- [New `queue_entire_balance_and_reset_validator`](#new-queue_entire_balance_and_reset_validator)
6766
- [New `compute_exit_epoch_and_update_churn`](#new-compute_exit_epoch_and_update_churn)
@@ -678,16 +677,6 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
678677
validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
679678
```
680679

681-
#### New `switch_to_compounding_validator`
682-
683-
```python
684-
def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None:
685-
validator = state.validators[index]
686-
if has_eth1_withdrawal_credential(validator):
687-
validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
688-
queue_excess_active_balance(state, index)
689-
```
690-
691680
#### New `queue_excess_active_balance`
692681

693682
```python
@@ -928,8 +917,6 @@ def process_pending_consolidations(state: BeaconState) -> None:
928917
if source_validator.withdrawable_epoch > next_epoch:
929918
break
930919

931-
# Churn any target excess active balance of target and raise its max
932-
switch_to_compounding_validator(state, pending_consolidation.target_index)
933920
# Move active balance to target. Excess balance is withdrawable.
934921
active_balance = get_active_balance(state, pending_consolidation.source_index)
935922
decrease_balance(state, pending_consolidation.source_index, active_balance)
@@ -1225,14 +1212,6 @@ def apply_deposit(state: BeaconState,
12251212
state.pending_balance_deposits.append(
12261213
PendingBalanceDeposit(index=index, amount=amount)
12271214
) # [Modified in Electra:EIP7251]
1228-
# Check if valid deposit switch to compounding credentials
1229-
if (
1230-
is_compounding_withdrawal_credential(withdrawal_credentials)
1231-
and has_eth1_withdrawal_credential(state.validators[index])
1232-
and is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature)
1233-
):
1234-
switch_to_compounding_validator(state, index)
1235-
12361215
```
12371216

12381217
###### New `is_valid_deposit_signature`
@@ -1431,10 +1410,6 @@ def process_consolidation_request(
14311410
source_validator = state.validators[source_index]
14321411
target_validator = state.validators[target_index]
14331412

1434-
# Verify that source != target, so a consolidation cannot be used as an exit.
1435-
if source_index == target_index:
1436-
return
1437-
14381413
# Verify source withdrawal credentials
14391414
has_correct_credential = has_execution_withdrawal_credential(source_validator)
14401415
is_correct_source_address = (
@@ -1459,12 +1434,22 @@ def process_consolidation_request(
14591434
if target_validator.exit_epoch != FAR_FUTURE_EPOCH:
14601435
return
14611436

1437+
# Churn any target excess active balance of target and raise its max
1438+
if has_eth1_withdrawal_credential(target_validator):
1439+
state.validators[target_index].withdrawal_credentials = (
1440+
COMPOUNDING_WITHDRAWAL_PREFIX + target_validator.withdrawal_credentials[1:])
1441+
queue_excess_active_balance(state, target_index)
1442+
1443+
# Verify that source != target, so a consolidation cannot be used as an exit.
1444+
if source_index == target_index:
1445+
return
1446+
14621447
# Initiate source validator exit and append pending consolidation
1463-
source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(
1448+
state.validators[source_index].exit_epoch = compute_consolidation_epoch_and_update_churn(
14641449
state, source_validator.effective_balance
14651450
)
1466-
source_validator.withdrawable_epoch = Epoch(
1467-
source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
1451+
state.validators[source_index].withdrawable_epoch = Epoch(
1452+
state.validators[source_index].exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
14681453
)
14691454
state.pending_consolidations.append(PendingConsolidation(
14701455
source_index=source_index,

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

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,37 @@ def test_consolidation_balance_through_two_churn_epochs(spec, state):
395395
assert state.consolidation_balance_to_consume == expected_balance
396396

397397

398-
# Failing tests
398+
@with_electra_and_later
399+
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
400+
@with_custom_state(
401+
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
402+
threshold_fn=default_activation_threshold,
403+
)
404+
@spec_test
405+
@single_phase
406+
def test_source_equals_target_switches_to_compounding(spec, state):
407+
current_epoch = spec.get_current_epoch(state)
408+
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
409+
410+
# Set source to eth1 credentials
411+
source_address = b"\x22" * 20
412+
set_eth1_withdrawal_credential_with_balance(
413+
spec, state, source_index, address=source_address
414+
)
415+
# Make consolidation from source to source
416+
consolidation = spec.ConsolidationRequest(
417+
source_address=source_address,
418+
source_pubkey=state.validators[source_index].pubkey,
419+
target_pubkey=state.validators[source_index].pubkey,
420+
)
421+
422+
# Check the the return condition
423+
assert consolidation.source_pubkey == consolidation.target_pubkey
424+
425+
yield from run_consolidation_processing(
426+
spec, state, consolidation, success=True
427+
)
428+
399429

400430
@with_electra_and_later
401431
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@@ -405,7 +435,7 @@ def test_consolidation_balance_through_two_churn_epochs(spec, state):
405435
)
406436
@spec_test
407437
@single_phase
408-
def test_incorrect_source_equals_target(spec, state):
438+
def test_source_equals_target_switches_to_compounding_with_excess(spec, state):
409439
current_epoch = spec.get_current_epoch(state)
410440
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
411441

@@ -414,6 +444,8 @@ def test_incorrect_source_equals_target(spec, state):
414444
set_eth1_withdrawal_credential_with_balance(
415445
spec, state, source_index, address=source_address
416446
)
447+
# Add excess balance
448+
state.balances[source_index] = state.balances[source_index] + spec.EFFECTIVE_BALANCE_INCREMENT
417449
# Make consolidation from source to source
418450
consolidation = spec.ConsolidationRequest(
419451
source_address=source_address,
@@ -425,10 +457,12 @@ def test_incorrect_source_equals_target(spec, state):
425457
assert consolidation.source_pubkey == consolidation.target_pubkey
426458

427459
yield from run_consolidation_processing(
428-
spec, state, consolidation, success=False
460+
spec, state, consolidation, success=True
429461
)
430462

431463

464+
# Failing tests
465+
432466
@with_electra_and_later
433467
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
434468
@with_custom_state(
@@ -815,36 +849,54 @@ def run_consolidation_processing(spec, state, consolidation, success=True):
815849
pre_exit_epoch_source = source_validator.exit_epoch
816850
pre_exit_epoch_target = target_validator.exit_epoch
817851
pre_pending_consolidations = state.pending_consolidations.copy()
852+
pre_target_withdrawal_credentials = target_validator.withdrawal_credentials
853+
pre_target_balance = state.balances[target_index]
818854
else:
819855
pre_state = state.copy()
820856

821857
yield 'pre', state
822858
yield 'consolidation_request', consolidation
823859

824860
spec.process_consolidation_request(state, consolidation)
861+
# print(state.validators[target_index].withdrawal_credentials)
825862

826863
yield 'post', state
827864

828865
if success:
829-
# Check source and target have execution credentials
866+
# Check source has execution credentials
830867
assert spec.has_execution_withdrawal_credential(source_validator)
868+
# Check target has compounding credentials
831869
assert spec.has_execution_withdrawal_credential(target_validator)
832870
# Check source address in the consolidation fits the withdrawal credentials
833871
assert source_validator.withdrawal_credentials[12:] == consolidation.source_address
834-
# Check source and target are not the same
835-
assert source_index != target_index
836872
# Check source and target were not exiting
837873
assert pre_exit_epoch_source == spec.FAR_FUTURE_EPOCH
838874
assert pre_exit_epoch_target == spec.FAR_FUTURE_EPOCH
839-
# Check source is now exiting
840-
assert state.validators[source_index].exit_epoch < spec.FAR_FUTURE_EPOCH
841-
# Check that the exit epoch matches earliest_consolidation_epoch
842-
assert state.validators[source_index].exit_epoch == state.earliest_consolidation_epoch
843-
# Check that the correct consolidation has been appended
844-
expected_new_pending_consolidation = spec.PendingConsolidation(
845-
source_index=source_index,
846-
target_index=target_index,
847-
)
848-
assert state.pending_consolidations == pre_pending_consolidations + [expected_new_pending_consolidation]
875+
# Check excess balance is queued if the target switched to compounding
876+
if pre_target_withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX:
877+
assert state.validators[target_index].withdrawal_credentials == (
878+
spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_target_withdrawal_credentials[1:])
879+
assert state.balances[target_index] == spec.MIN_ACTIVATION_BALANCE
880+
if pre_target_balance > spec.MIN_ACTIVATION_BALANCE:
881+
assert state.pending_balance_deposits == [spec.PendingBalanceDeposit(
882+
index=target_index, amount=(pre_target_balance - spec.MIN_ACTIVATION_BALANCE))]
883+
# If source and target are same, no consolidation must have been initiated
884+
if source_index == target_index:
885+
assert state.validators[source_index].exit_epoch == spec.FAR_FUTURE_EPOCH
886+
assert state.pending_consolidations == []
887+
else:
888+
# Check source is now exiting
889+
assert state.validators[source_index].exit_epoch < spec.FAR_FUTURE_EPOCH
890+
# Check that the exit epoch matches earliest_consolidation_epoch
891+
assert state.validators[source_index].exit_epoch == state.earliest_consolidation_epoch
892+
# Check that the withdrawable_epoch is set correctly
893+
assert state.validators[source_index].withdrawable_epoch == (
894+
state.validators[source_index].exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
895+
# Check that the correct consolidation has been appended
896+
expected_new_pending_consolidation = spec.PendingConsolidation(
897+
source_index=source_index,
898+
target_index=target_index,
899+
)
900+
assert state.pending_consolidations == pre_pending_consolidations + [expected_new_pending_consolidation]
849901
else:
850902
assert pre_state == state

tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ def test_basic_pending_consolidation(spec, state):
3636
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
3737

3838
# Pending consolidation was successfully processed
39-
assert (
40-
state.validators[target_index].withdrawal_credentials[:1]
41-
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
42-
)
4339
assert state.balances[target_index] == 2 * spec.MIN_ACTIVATION_BALANCE
4440
assert state.balances[source_index] == 0
4541
assert state.pending_consolidations == []
@@ -65,21 +61,13 @@ def test_consolidation_not_yet_withdrawable_validator(spec, state):
6561

6662
pre_pending_consolidations = state.pending_consolidations.copy()
6763
pre_balances = state.balances.copy()
68-
pre_target_withdrawal_credential = state.validators[
69-
target_index
70-
].withdrawal_credentials[:1]
7164

7265
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
7366

7467
# Pending consolidation is not processed
7568
# Balances are unchanged
7669
assert state.balances[source_index] == pre_balances[0]
7770
assert state.balances[target_index] == pre_balances[1]
78-
# Target withdrawal credential is unchanged
79-
assert (
80-
state.validators[target_index].withdrawal_credentials[:1]
81-
== pre_target_withdrawal_credential
82-
)
8371
# Pending consolidation is still in the queue
8472
assert state.pending_consolidations == pre_pending_consolidations
8573

@@ -121,17 +109,9 @@ def test_skip_consolidation_when_source_slashed(spec, state):
121109
# first pending consolidation should not be processed
122110
assert state.balances[target0_index] == spec.MIN_ACTIVATION_BALANCE
123111
assert state.balances[source0_index] == spec.MIN_ACTIVATION_BALANCE
124-
assert (
125-
state.validators[target0_index].withdrawal_credentials[:1]
126-
== spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX
127-
)
128112
# second pending consolidation should be processed: first one is skipped and doesn't block the queue
129113
assert state.balances[target1_index] == 2 * spec.MIN_ACTIVATION_BALANCE
130114
assert state.balances[source1_index] == 0
131-
assert (
132-
state.validators[target1_index].withdrawal_credentials[:1]
133-
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
134-
)
135115

136116

137117
@with_electra_and_later
@@ -167,26 +147,14 @@ def test_all_consolidation_cases_together(spec, state):
167147
spec.initiate_validator_exit(state, 2)
168148

169149
pre_balances = state.balances.copy()
170-
pre_target_withdrawal_prefixes = [
171-
state.validators[target_index[i]].withdrawal_credentials[:1]
172-
for i in [0, 1, 2, 3]
173-
]
174150
pre_pending_consolidations = state.pending_consolidations.copy()
175151
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
176152

177153
# First consolidation is successfully processed
178-
assert (
179-
state.validators[target_index[0]].withdrawal_credentials[:1]
180-
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
181-
)
182154
assert state.balances[target_index[0]] == 2 * spec.MIN_ACTIVATION_BALANCE
183155
assert state.balances[source_index[0]] == 0
184156
# All other consolidations are not processed
185157
for i in [1, 2, 3]:
186-
assert (
187-
state.validators[target_index[i]].withdrawal_credentials[:1]
188-
== pre_target_withdrawal_prefixes[i]
189-
)
190158
assert state.balances[source_index[i]] == pre_balances[source_index[i]]
191159
assert state.balances[target_index[i]] == pre_balances[target_index[i]]
192160
# First consolidation is processed, second is skipped, last two are left in the queue
@@ -226,22 +194,11 @@ def test_pending_consolidation_future_epoch(spec, state):
226194

227195
# Pending consolidation was successfully processed
228196
expected_source_balance = state_before_consolidation.balances[source_index] - spec.MIN_ACTIVATION_BALANCE
229-
assert (
230-
state.validators[target_index].withdrawal_credentials[:1]
231-
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
232-
)
233-
assert state.balances[target_index] == 2 * spec.MIN_ACTIVATION_BALANCE
197+
expected_target_balance = state_before_consolidation.balances[target_index] + spec.MIN_ACTIVATION_BALANCE
234198
assert state.balances[source_index] == expected_source_balance
199+
assert state.balances[target_index] == expected_target_balance
235200
assert state.pending_consolidations == []
236201

237-
# Pending balance deposit to the target is created as part of `switch_to_compounding_validator`.
238-
# The excess balance to queue are the rewards accumulated over the previous epoch transitions.
239-
expected_pending_balance = state_before_consolidation.balances[target_index] - spec.MIN_ACTIVATION_BALANCE
240-
assert len(state.pending_balance_deposits) > 0
241-
pending_balance_deposit = state.pending_balance_deposits[len(state.pending_balance_deposits) - 1]
242-
assert pending_balance_deposit.index == target_index
243-
assert pending_balance_deposit.amount == expected_pending_balance
244-
245202

246203
@with_electra_and_later
247204
@spec_state_test
@@ -280,10 +237,6 @@ def test_pending_consolidation_compounding_creds(spec, state):
280237
expected_target_balance = (
281238
state_before_consolidation.balances[source_index] + state_before_consolidation.balances[target_index]
282239
)
283-
assert (
284-
state.validators[target_index].withdrawal_credentials[:1]
285-
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
286-
)
287240
assert state.balances[target_index] == expected_target_balance
288241
# All source balance is active and moved to the target,
289242
# because the source validator has compounding credentials
@@ -336,10 +289,6 @@ def test_pending_consolidation_with_pending_deposit(spec, state):
336289
expected_target_balance = (
337290
state_before_consolidation.balances[source_index] + state_before_consolidation.balances[target_index]
338291
)
339-
assert (
340-
state.validators[target_index].withdrawal_credentials[:1]
341-
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
342-
)
343292
assert state.balances[target_index] == expected_target_balance
344293
assert state.balances[source_index] == 0
345294
assert state.pending_consolidations == []

0 commit comments

Comments
 (0)