Merge pull request #2580 from ralexstokes/add-rewards-test-case-for-exited-vals

Add test cases to ensure coverage with exited validators
This commit is contained in:
Danny Ryan 2021-09-07 16:11:53 -06:00 committed by GitHub
commit 4faff4f899
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 285 additions and 69 deletions

View File

@ -12,7 +12,6 @@ from eth2spec.test.helpers.constants import (
from eth2spec.test.helpers.sync_committee import (
compute_aggregate_sync_committee_signature,
compute_committee_indices,
get_committee_indices,
run_sync_committee_processing,
run_successful_sync_committee_test,
)
@ -28,7 +27,7 @@ from eth2spec.test.context import (
@spec_state_test
@always_bls
def test_invalid_signature_bad_domain(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
block = build_empty_block_for_next_slot(spec, state)
block.body.sync_aggregate = spec.SyncAggregate(
@ -48,7 +47,7 @@ def test_invalid_signature_bad_domain(spec, state):
@spec_state_test
@always_bls
def test_invalid_signature_missing_participant(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(2020)
random_participant = rng.choice(committee_indices)
@ -111,7 +110,7 @@ def test_invalid_signature_infinite_signature_with_single_participant(spec, stat
@spec_state_test
@always_bls
def test_invalid_signature_extra_participant(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(3030)
random_participant = rng.choice(committee_indices)
@ -134,7 +133,7 @@ def test_invalid_signature_extra_participant(spec, state):
@with_presets([MINIMAL], reason="to create nonduplicate committee")
@spec_state_test
def test_sync_committee_rewards_nonduplicate_committee(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=False)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [True] * committee_size
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
@ -150,7 +149,7 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state):
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=True)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [False] * committee_size
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
@ -166,7 +165,7 @@ def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
def test_sync_committee_rewards_duplicate_committee_half_participation(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=True)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2)
assert len(committee_bits) == committee_size
@ -183,7 +182,7 @@ def test_sync_committee_rewards_duplicate_committee_half_participation(spec, sta
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
def test_sync_committee_rewards_duplicate_committee_full_participation(spec, state):
committee_indices = get_committee_indices(spec, state, duplicates=True)
committee_indices = compute_committee_indices(spec, state)
committee_size = len(committee_indices)
committee_bits = [True] * committee_size
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
@ -199,7 +198,7 @@ def test_sync_committee_rewards_duplicate_committee_full_participation(spec, sta
@spec_state_test
@always_bls
def test_sync_committee_rewards_not_full_participants(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
rng = random.Random(1010)
committee_bits = [rng.choice([True, False]) for _ in committee_indices]
@ -210,7 +209,7 @@ def test_sync_committee_rewards_not_full_participants(spec, state):
@spec_state_test
@always_bls
def test_sync_committee_rewards_empty_participants(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
committee_bits = [False for _ in committee_indices]
yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits)
@ -220,7 +219,7 @@ def test_sync_committee_rewards_empty_participants(spec, state):
@spec_state_test
@always_bls
def test_invalid_signature_past_block(spec, state):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_indices = compute_committee_indices(spec, state)
for _ in range(2):
# NOTE: need to transition twice to move beyond the degenerate case at genesis

View File

@ -2,10 +2,19 @@ import random
from eth2spec.test.helpers.constants import (
MAINNET, MINIMAL,
)
from eth2spec.test.helpers.random import (
randomize_state,
)
from eth2spec.test.helpers.state import (
has_active_balance_differential,
)
from eth2spec.test.helpers.sync_committee import (
get_committee_indices,
compute_committee_indices,
run_successful_sync_committee_test,
)
from eth2spec.test.helpers.voluntary_exits import (
get_unslashed_exited_validators,
)
from eth2spec.test.context import (
with_altair_and_later,
spec_state_test,
@ -18,8 +27,8 @@ from eth2spec.test.context import (
)
def _test_harness_for_randomized_test_case(spec, state, duplicates=False, participation_fn=None):
committee_indices = get_committee_indices(spec, state, duplicates=duplicates)
def _test_harness_for_randomized_test_case(spec, state, expect_duplicates=False, participation_fn=None):
committee_indices = compute_committee_indices(spec, state)
if participation_fn:
participating_indices = participation_fn(committee_indices)
@ -28,7 +37,7 @@ def _test_harness_for_randomized_test_case(spec, state, duplicates=False, partic
committee_bits = [index in participating_indices for index in committee_indices]
committee_size = len(committee_indices)
if duplicates:
if expect_duplicates:
assert committee_size > len(set(committee_indices))
else:
assert committee_size == len(set(committee_indices))
@ -44,7 +53,7 @@ def test_random_only_one_participant_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: [rng.choice(comm)],
)
@ -57,7 +66,7 @@ def test_random_low_participation_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)),
)
@ -70,7 +79,7 @@ def test_random_high_participation_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)),
)
@ -83,7 +92,7 @@ def test_random_all_but_one_participating_with_duplicates(spec, state):
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, len(comm) - 1),
)
@ -98,7 +107,25 @@ def test_random_misc_balances_and_half_participation_with_duplicates(spec, state
yield from _test_harness_for_randomized_test_case(
spec,
state,
duplicates=True,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)
@with_altair_and_later
@with_presets([MAINNET], reason="to create duplicate committee")
@spec_state_test
@single_phase
def test_random_with_exits_with_duplicates(spec, state):
rng = random.Random(1402)
randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from _test_harness_for_randomized_test_case(
spec,
state,
expect_duplicates=True,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)
@ -163,3 +190,20 @@ def test_random_misc_balances_and_half_participation_without_duplicates(spec, st
state,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)
@with_altair_and_later
@with_presets([MINIMAL], reason="to create nonduplicate committee")
@spec_state_test
@single_phase
def test_random_with_exits_without_duplicates(spec, state):
rng = random.Random(1502)
randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from _test_harness_for_randomized_test_case(
spec,
state,
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
)

View File

@ -128,3 +128,34 @@ def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fra
exit_random_validators(spec, state, rng, fraction=exit_fraction)
slash_random_validators(spec, state, rng, fraction=slash_fraction)
randomize_attestation_participation(spec, state, rng)
def patch_state_to_non_leaking(spec, state):
"""
This function performs an irregular state transition so that:
1. the current justified checkpoint references the previous epoch
2. the previous justified checkpoint references the epoch before previous
3. the finalized checkpoint matches the previous justified checkpoint
The effects of this function are intended to offset randomization side effects
performed by other functionality in this module so that if the ``state`` was leaking,
then the ``state`` is not leaking after.
"""
state.justification_bits[0] = True
state.justification_bits[1] = True
previous_epoch = spec.get_previous_epoch(state)
previous_root = spec.get_block_root(state, previous_epoch)
previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1))
previous_previous_root = spec.get_block_root(state, previous_previous_epoch)
state.previous_justified_checkpoint = spec.Checkpoint(
epoch=previous_previous_epoch,
root=previous_previous_root,
)
state.current_justified_checkpoint = spec.Checkpoint(
epoch=previous_epoch,
root=previous_root,
)
state.finalized_checkpoint = spec.Checkpoint(
epoch=previous_previous_epoch,
root=previous_previous_root,
)

View File

@ -255,7 +255,19 @@ def run_get_inactivity_penalty_deltas(spec, state):
else:
assert penalties[index] > base_penalty
else:
assert penalties[index] == 0
if not is_post_altair(spec):
assert penalties[index] == 0
continue
else:
# post altair, this penalty is derived from the inactivity score
# regardless if the state is leaking or not...
if index in matching_attesting_indices:
assert penalties[index] == 0
else:
# copied from spec:
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR
assert penalties[index] == penalty_numerator // penalty_denominator
def transition_state_to_leak(spec, state, epochs=None):

View File

@ -1,6 +1,6 @@
from eth2spec.test.context import expect_assertion_error, is_post_altair
from eth2spec.test.helpers.block import apply_empty_block, sign_block, transition_unsigned_block
from eth2spec.test.helpers.voluntary_exits import get_exited_validators
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
def get_balance(state, index):
@ -142,7 +142,7 @@ def ensure_state_has_validators_across_lifecycle(spec, state):
for each of the following lifecycle states:
1. Pending / deposited
2. Active
3. Exited
3. Exited (but not slashed)
4. Slashed
"""
has_pending = any(filter(spec.is_eligible_for_activation_queue, state.validators))
@ -150,8 +150,18 @@ def ensure_state_has_validators_across_lifecycle(spec, state):
current_epoch = spec.get_current_epoch(state)
has_active = any(filter(lambda v: spec.is_active_validator(v, current_epoch), state.validators))
has_exited = any(get_exited_validators(spec, state))
has_exited = any(get_unslashed_exited_validators(spec, state))
has_slashed = any(filter(lambda v: v.slashed, state.validators))
return has_pending and has_active and has_exited and has_slashed
def has_active_balance_differential(spec, state):
"""
Ensure there is a difference between the total balance of
all _active_ validators and _all_ validators.
"""
active_balance = spec.get_total_active_balance(state)
total_balance = spec.get_total_balance(state, set(range(len(state.validators))))
return active_balance // spec.EFFECTIVE_BALANCE_INCREMENT != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT

View File

@ -9,7 +9,6 @@ from eth2spec.test.helpers.block import (
)
from eth2spec.test.helpers.block_processing import run_block_processing_to
from eth2spec.utils import bls
from eth2spec.utils.hash_function import hash
def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None, domain_type=None):
@ -75,10 +74,12 @@ def compute_sync_committee_proposer_reward(spec, state, committee_indices, commi
return spec.Gwei(participant_reward * participant_number)
def compute_committee_indices(spec, state, committee):
def compute_committee_indices(spec, state, committee=None):
"""
Given a ``committee``, calculate and return the related indices
"""
if committee is None:
committee = state.current_sync_committee
all_pubkeys = [v.pubkey for v in state.validators]
return [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys]
@ -153,6 +154,7 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in
state,
block.slot - 1,
[index for index, bit in zip(committee_indices, committee_bits) if bit],
block_root=block.parent_root,
)
)
return block
@ -161,23 +163,3 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in
def run_successful_sync_committee_test(spec, state, committee_indices, committee_bits):
block = _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits)
yield from run_sync_committee_processing(spec, state, block)
def get_committee_indices(spec, state, duplicates=False):
"""
This utility function allows the caller to ensure there are or are not
duplicate validator indices in the returned committee based on
the boolean ``duplicates``.
"""
state = state.copy()
current_epoch = spec.get_current_epoch(state)
randao_index = (current_epoch + 1) % spec.EPOCHS_PER_HISTORICAL_VECTOR
while True:
committee = spec.get_next_sync_committee_indices(state)
if duplicates:
if len(committee) != len(set(committee)):
return committee
else:
if len(committee) == len(set(committee)):
return committee
state.randao_mixes[randao_index] = hash(state.randao_mixes[randao_index])

View File

@ -34,6 +34,13 @@ def get_exited_validators(spec, state):
return [index for (index, validator) in enumerate(state.validators) if validator.exit_epoch <= current_epoch]
def get_unslashed_exited_validators(spec, state):
return [
index for index in get_exited_validators(spec, state)
if not state.validators[index].slashed
]
def exit_validators(spec, state, validator_count, rng=None):
if rng is None:
rng = Random(1337)

View File

@ -1,8 +1,10 @@
from random import Random
from eth2spec.test.context import is_post_altair, spec_state_test, with_all_phases
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with,
)
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.helpers.state import transition_to, next_epoch_via_block, next_slot
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
def run_process_just_and_fin(spec, state):
@ -300,3 +302,76 @@ def test_12_ok_support_messed_target(spec, state):
@spec_state_test
def test_12_poor_support(spec, state):
yield from finalize_on_12(spec, state, 3, False, False)
@with_all_phases
@spec_state_test
def test_balance_threshold_with_exited_validators(spec, state):
"""
This test exercises a very specific failure mode where
exited validators are incorrectly included in the total active balance
when weighing justification.
"""
rng = Random(133333)
# move past genesis conditions
for _ in range(3):
next_epoch_via_block(spec, state)
# mock attestation helper requires last slot of epoch
for _ in range(spec.SLOTS_PER_EPOCH - 1):
next_slot(spec, state)
# Step 1: Exit ~1/2 vals in current epoch
epoch = spec.get_current_epoch(state)
for index in spec.get_active_validator_indices(state, epoch):
if rng.choice([True, False]):
continue
validator = state.validators[index]
validator.exit_epoch = epoch
validator.withdrawable_epoch = epoch + 1
validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
exited_validators = get_unslashed_exited_validators(spec, state)
assert len(exited_validators) != 0
source = state.current_justified_checkpoint
target = spec.Checkpoint(
epoch=epoch,
root=spec.get_block_root(state, epoch)
)
add_mock_attestations(
spec,
state,
epoch,
source,
target,
sufficient_support=False,
)
if not is_post_altair(spec):
current_attestations = spec.get_matching_target_attestations(state, epoch)
total_active_balance = spec.get_total_active_balance(state)
current_target_balance = spec.get_attesting_balance(state, current_attestations)
# Check we will not justify the current checkpoint
does_justify = current_target_balance * 3 >= total_active_balance * 2
assert not does_justify
# Ensure we would have justified the current checkpoint w/ the exited validators
current_exited_balance = spec.get_total_balance(state, exited_validators)
does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2
assert does_justify
else:
current_indices = spec.get_unslashed_participating_indices(state, spec.TIMELY_TARGET_FLAG_INDEX, epoch)
total_active_balance = spec.get_total_active_balance(state)
current_target_balance = spec.get_total_balance(state, current_indices)
# Check we will not justify the current checkpoint
does_justify = current_target_balance * 3 >= total_active_balance * 2
assert not does_justify
# Ensure we would have justified the current checkpoint w/ the exited validators
current_exited_balance = spec.get_total_balance(state, exited_validators)
does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2
assert does_justify
yield from run_process_just_and_fin(spec, state)
assert state.current_justified_checkpoint.epoch != epoch

View File

@ -1,7 +1,11 @@
from random import Random
from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with, run_epoch_processing_to
)
from eth2spec.test.helpers.random import randomize_state
from eth2spec.test.helpers.state import has_active_balance_differential
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
from eth2spec.test.helpers.state import next_epoch
@ -22,6 +26,9 @@ def slash_validators(spec, state, indices, out_epochs):
spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR
] = total_slashed_balance
# verify some slashings happened...
assert total_slashed_balance != 0
def get_slashing_multiplier(spec):
if is_post_altair(spec):
@ -30,9 +37,7 @@ def get_slashing_multiplier(spec):
return spec.PROPORTIONAL_SLASHING_MULTIPLIER
@with_all_phases
@spec_state_test
def test_max_penalties(spec, state):
def _setup_process_slashings_test(spec, state, not_slashable_set=set()):
# Slashed count to ensure that enough validators are slashed to induce maximum penalties
slashed_count = min(
(len(state.validators) // get_slashing_multiplier(spec)) + 1,
@ -41,14 +46,23 @@ def test_max_penalties(spec, state):
)
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
slashed_indices = list(range(slashed_count))
slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count)
eligible_indices = set(range(slashed_count))
slashed_indices = eligible_indices.difference(not_slashable_set)
slash_validators(spec, state, sorted(slashed_indices), [out_epoch] * slashed_count)
total_balance = spec.get_total_active_balance(state)
total_penalties = sum(state.slashings)
assert total_balance // get_slashing_multiplier(spec) <= total_penalties
return slashed_indices
@with_all_phases
@spec_state_test
def test_max_penalties(spec, state):
slashed_indices = _setup_process_slashings_test(spec, state)
yield from run_process_slashings(spec, state)
for i in slashed_indices:
@ -171,3 +185,28 @@ def test_scaled_penalties(spec, state):
* spec.EFFECTIVE_BALANCE_INCREMENT
)
assert state.balances[i] == pre_slash_balances[i] - expected_penalty
@with_all_phases
@spec_state_test
def test_slashings_with_random_state(spec, state):
rng = Random(9998)
randomize_state(spec, state, rng)
pre_balances = state.balances.copy()
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
slashed_indices = _setup_process_slashings_test(spec, state, not_slashable_set=target_validators)
# ensure no accidental slashings of protected set...
current_target_validators = get_unslashed_exited_validators(spec, state)
assert len(current_target_validators) != 0
assert current_target_validators == target_validators
yield from run_process_slashings(spec, state)
for i in slashed_indices:
assert state.balances[i] < pre_balances[i]

View File

@ -9,6 +9,9 @@ from eth2spec.test.context import (
low_balances, misc_balances,
)
import eth2spec.test.helpers.rewards as rewards_helpers
from eth2spec.test.helpers.random import randomize_state, patch_state_to_non_leaking
from eth2spec.test.helpers.state import has_active_balance_differential
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
@with_all_phases
@ -35,6 +38,21 @@ def test_full_random_3(spec, state):
yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(4040))
@with_all_phases
@spec_state_test
def test_full_random_4(spec, state):
"""
Ensure a rewards test with some exited (but not slashed) validators.
"""
rng = Random(5050)
randomize_state(spec, state, rng)
assert spec.is_in_inactivity_leak(state)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from rewards_helpers.run_deltas(spec, state)
@with_all_phases
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@ -57,3 +75,17 @@ def test_full_random_low_balances_1(spec, state):
@single_phase
def test_full_random_misc_balances(spec, state):
yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(7070))
@with_all_phases
@spec_state_test
def test_full_random_without_leak_0(spec, state):
rng = Random(1010)
randomize_state(spec, state, rng)
assert spec.is_in_inactivity_leak(state)
patch_state_to_non_leaking(spec, state)
assert not spec.is_in_inactivity_leak(state)
target_validators = get_unslashed_exited_validators(spec, state)
assert len(target_validators) != 0
assert has_active_balance_differential(spec, state)
yield from rewards_helpers.run_deltas(spec, state)

View File

@ -17,6 +17,7 @@ from eth2spec.test.helpers.inactivity_scores import (
)
from eth2spec.test.helpers.random import (
randomize_state as randomize_state_helper,
patch_state_to_non_leaking,
)
from eth2spec.test.helpers.state import (
next_slot,
@ -274,23 +275,7 @@ def _randomized_scenario_setup(state_randomizer):
may not reflect this condition with prior (arbitrary) mutations,
so this mutator addresses that fact.
"""
state.justification_bits = (True, True, True, True)
previous_epoch = spec.get_previous_epoch(state)
previous_root = spec.get_block_root(state, previous_epoch)
previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1))
previous_previous_root = spec.get_block_root(state, previous_previous_epoch)
state.previous_justified_checkpoint = spec.Checkpoint(
epoch=previous_previous_epoch,
root=previous_previous_root,
)
state.current_justified_checkpoint = spec.Checkpoint(
epoch=previous_epoch,
root=previous_root,
)
state.finalized_checkpoint = spec.Checkpoint(
epoch=previous_previous_epoch,
root=previous_previous_root,
)
patch_state_to_non_leaking(spec, state)
return (
# NOTE: the block randomization function assumes at least 1 shard committee period