diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index bd52ce727..0eaddbb88 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py index 75845e060..903df4081 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py @@ -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), + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 8f095aebb..b24d3c8c7 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -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, + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 1867db08f..ec617bda9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -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): diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index b4c9e1d67..6f1923e54 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py index e59f679e1..417802ece 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py +++ b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py @@ -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]) diff --git a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py index 73d4598b3..55ea0b5b0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py @@ -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) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py index 9db6076f8..1d3197ba6 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index e336ebef7..1b977640d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -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] diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index 1184a6617..44f22270b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -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) diff --git a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py index ce99c1053..02a5464f7 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -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