Skip to content

Commit fcca2b5

Browse files
committed
Fix off-by-one in process_pending_consolidations
1 parent 9515f3e commit fcca2b5

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

specs/electra/beacon-chain.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,13 +894,14 @@ def process_pending_balance_deposits(state: BeaconState) -> None:
894894

895895
```python
896896
def process_pending_consolidations(state: BeaconState) -> None:
897+
next_epoch = Epoch(get_current_epoch(state) + 1)
897898
next_pending_consolidation = 0
898899
for pending_consolidation in state.pending_consolidations:
899900
source_validator = state.validators[pending_consolidation.source_index]
900901
if source_validator.slashed:
901902
next_pending_consolidation += 1
902903
continue
903-
if source_validator.withdrawable_epoch > get_current_epoch(state):
904+
if source_validator.withdrawable_epoch > next_epoch:
904905
break
905906

906907
# Churn any target excess active balance of target and raise its max

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

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
1+
from eth2spec.test.helpers.epoch_processing import (
2+
run_epoch_processing_with,
3+
compute_state_by_epoch_processing_to,
4+
)
25
from eth2spec.test.context import (
36
spec_state_test,
47
with_electra_and_later,
58
)
9+
from eth2spec.test.helpers.state import (
10+
next_epoch_with_full_participation,
11+
)
612

713
# ***********************
814
# * CONSOLIDATION TESTS *
@@ -185,3 +191,101 @@ def test_all_consolidation_cases_together(spec, state):
185191
assert state.balances[target_index[i]] == pre_balances[target_index[i]]
186192
# First consolidation is processed, second is skipped, last two are left in the queue
187193
state.pending_consolidations = pre_pending_consolidations[2:]
194+
195+
196+
@with_electra_and_later
197+
@spec_state_test
198+
def test_pending_consolidation_future_epoch(spec, state):
199+
current_epoch = spec.get_current_epoch(state)
200+
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
201+
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
202+
# initiate source exit
203+
spec.initiate_validator_exit(state, source_index)
204+
# set withdrawable_epoch to exit_epoch + 1
205+
state.validators[source_index].withdrawable_epoch = state.validators[source_index].exit_epoch + spec.Epoch(1)
206+
# append pending consolidation
207+
state.pending_consolidations.append(
208+
spec.PendingConsolidation(source_index=source_index, target_index=target_index)
209+
)
210+
# Set the target withdrawal credential to eth1
211+
eth1_withdrawal_credential = (
212+
spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20
213+
)
214+
state.validators[target_index].withdrawal_credentials = eth1_withdrawal_credential
215+
216+
# Advance to withdrawable_epoch - 1 with full participation
217+
target_epoch = state.validators[source_index].withdrawable_epoch - spec.Epoch(1)
218+
while spec.get_current_epoch(state) < target_epoch:
219+
next_epoch_with_full_participation(spec, state)
220+
221+
# Obtain state before the call to process_pending_consolidations
222+
state_before_consolidation = compute_state_by_epoch_processing_to(spec, state, "process_pending_consolidations")
223+
224+
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
225+
226+
# Pending consolidation was successfully processed
227+
expected_source_balance = state_before_consolidation.balances[source_index] - spec.MIN_ACTIVATION_BALANCE
228+
assert (
229+
state.validators[target_index].withdrawal_credentials[:1]
230+
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
231+
)
232+
assert state.balances[target_index] == 2 * spec.MIN_ACTIVATION_BALANCE
233+
assert state.balances[source_index] == expected_source_balance
234+
assert state.pending_consolidations == []
235+
236+
# Pending balance deposit to the target is created
237+
expected_pending_balance = state_before_consolidation.balances[target_index] - spec.MIN_ACTIVATION_BALANCE
238+
assert len(state.pending_balance_deposits) > 0
239+
pending_balance_deposit = state.pending_balance_deposits[len(state.pending_balance_deposits) - 1]
240+
assert pending_balance_deposit.index == target_index
241+
assert pending_balance_deposit.amount == expected_pending_balance
242+
243+
244+
@with_electra_and_later
245+
@spec_state_test
246+
def test_pending_consolidation_compounding_creds(spec, state):
247+
current_epoch = spec.get_current_epoch(state)
248+
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
249+
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
250+
# initiate source exit
251+
spec.initiate_validator_exit(state, source_index)
252+
# set withdrawable_epoch to exit_epoch + 1
253+
state.validators[source_index].withdrawable_epoch = state.validators[source_index].exit_epoch + spec.Epoch(1)
254+
# append pending consolidation
255+
state.pending_consolidations.append(
256+
spec.PendingConsolidation(source_index=source_index, target_index=target_index)
257+
)
258+
# Set the source and the target withdrawal credential to compounding
259+
compounding_withdrawal_credential_1 = (
260+
spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20
261+
)
262+
compounding_withdrawal_credential_2 = (
263+
spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x12" * 20
264+
)
265+
state.validators[source_index].withdrawal_credentials = compounding_withdrawal_credential_1
266+
state.validators[target_index].withdrawal_credentials = compounding_withdrawal_credential_2
267+
268+
# Advance to withdrawable_epoch - 1 with full participation
269+
target_epoch = state.validators[source_index].withdrawable_epoch - spec.Epoch(1)
270+
while spec.get_current_epoch(state) < target_epoch:
271+
next_epoch_with_full_participation(spec, state)
272+
273+
# Obtain state before the call to process_pending_consolidations
274+
state_before_consolidation = compute_state_by_epoch_processing_to(spec, state, "process_pending_consolidations")
275+
276+
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
277+
278+
# Pending consolidation was successfully processed
279+
expected_target_balance = (
280+
state_before_consolidation.balances[source_index] + state_before_consolidation.balances[target_index]
281+
)
282+
assert (
283+
state.validators[target_index].withdrawal_credentials[:1]
284+
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
285+
)
286+
assert state.balances[target_index] == expected_target_balance
287+
assert state.balances[source_index] == 0
288+
assert state.pending_consolidations == []
289+
290+
# Pending balance deposit to the target is not created
291+
assert len(state.pending_balance_deposits) == 0

tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def get_process_calls(spec):
2222
'charge_confirmed_header_fees', # sharding
2323
'reset_pending_headers', # sharding
2424
'process_eth1_data_reset',
25+
'process_pending_balance_deposits', # electra
26+
'process_pending_consolidations', # electra
2527
'process_effective_balance_updates',
2628
'process_slashings_reset',
2729
'process_randao_mixes_reset',
@@ -72,3 +74,9 @@ def run_epoch_processing_with(spec, state, process_name: str):
7274
yield 'pre', state
7375
getattr(spec, process_name)(state)
7476
yield 'post', state
77+
78+
79+
def compute_state_by_epoch_processing_to(spec, state, process_name: str):
80+
state_copy = state.copy()
81+
run_epoch_processing_to(spec, state_copy, process_name)
82+
return state_copy

tests/core/pyspec/eth2spec/test/helpers/state.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ def next_epoch(spec, state):
6060
spec.process_slots(state, slot)
6161

6262

63+
def next_epoch_with_full_participation(spec, state):
64+
"""
65+
Transition to the start slot of the next epoch with full participation
66+
"""
67+
set_full_participation(spec, state)
68+
next_epoch(spec, state)
69+
70+
6371
def next_epoch_via_block(spec, state, insert_state_root=False):
6472
"""
6573
Transition to the start slot of the next epoch via a full block transition

0 commit comments

Comments
 (0)