diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index 1cc8cb9ae..cbee4249c 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -1,26 +1,20 @@ from random import Random from eth2spec.test.context import spec_state_test, with_altair_and_later -from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores +from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores, zero_inactivity_scores from eth2spec.test.helpers.state import ( next_epoch_via_block, + set_full_participation, set_full_participation_previous_epoch, + set_empty_participation, ) from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with ) from eth2spec.test.helpers.random import ( randomize_attestation_participation, + randomize_previous_epoch_participation, ) - - -def set_full_participation(spec, state): - full_flags = spec.ParticipationFlags(0) - for flag_index in range(len(spec.PARTICIPATION_FLAG_WEIGHTS)): - full_flags = spec.add_flag(full_flags, flag_index) - - for index in range(len(state.validators)): - state.current_epoch_participation[index] = full_flags - state.previous_epoch_participation[index] = full_flags +from eth2spec.test.helpers.rewards import leaking def run_process_inactivity_updates(spec, state): @@ -33,58 +27,169 @@ def test_genesis(spec, state): yield from run_process_inactivity_updates(spec, state) +@with_altair_and_later +@spec_state_test +def test_genesis_random_scores(spec, state): + rng = Random(10102) + state.inactivity_scores = [rng.randint(0, 100) for _ in state.inactivity_scores] + pre_scores = state.inactivity_scores.copy() + + yield from run_process_inactivity_updates(spec, state) + + assert state.inactivity_scores == pre_scores + + # # Genesis epoch processing is skipped # Thus all of following tests all go past genesis epoch to test core functionality # +def run_inactivity_scores_test(spec, state, participation_fn=None, inactivity_scores_fn=None, rng=Random(10101)): + next_epoch_via_block(spec, state) + if participation_fn is not None: + participation_fn(spec, state, rng=rng) + if inactivity_scores_fn is not None: + inactivity_scores_fn(spec, state, rng=rng) + yield from run_process_inactivity_updates(spec, state) + + @with_altair_and_later @spec_state_test def test_all_zero_inactivity_scores_empty_participation(spec, state): - next_epoch_via_block(spec, state) - state.inactivity_scores = [0] * len(state.validators) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) + assert set(state.inactivity_scores) == set([0]) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_all_zero_inactivity_scores_empty_participation_leaking(spec, state): + yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) + + # Should still in be leak + assert spec.is_in_inactivity_leak(state) + + for score in state.inactivity_scores: + assert score > 0 @with_altair_and_later @spec_state_test def test_all_zero_inactivity_scores_random_participation(spec, state): - next_epoch_via_block(spec, state) - state.inactivity_scores = [0] * len(state.validators) - randomize_attestation_participation(spec, state, rng=Random(5555)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + randomize_attestation_participation, zero_inactivity_scores, rng=Random(5555), + ) + assert set(state.inactivity_scores) == set([0]) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_all_zero_inactivity_scores_random_participation_leaking(spec, state): + # Only randompize participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + randomize_previous_epoch_participation, zero_inactivity_scores, rng=Random(5555), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) + + assert 0 in state.inactivity_scores + assert len(set(state.inactivity_scores)) > 1 @with_altair_and_later @spec_state_test def test_all_zero_inactivity_scores_full_participation(spec, state): - next_epoch_via_block(spec, state) - set_full_participation(spec, state) - state.inactivity_scores = [0] * len(state.validators) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + set_full_participation, zero_inactivity_scores, + ) + + assert set(state.inactivity_scores) == set([0]) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_all_zero_inactivity_scores_full_participation_leaking(spec, state): + # Only set full participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + set_full_participation_previous_epoch, zero_inactivity_scores, + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) + + assert set(state.inactivity_scores) == set([0]) @with_altair_and_later @spec_state_test def test_random_inactivity_scores_empty_participation(spec, state): - next_epoch_via_block(spec, state) - randomize_inactivity_scores(spec, state, rng=Random(9999)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + set_empty_participation, randomize_inactivity_scores, Random(9999), + ) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_random_inactivity_scores_empty_participation_leaking(spec, state): + yield from run_inactivity_scores_test( + spec, state, + set_empty_participation, randomize_inactivity_scores, Random(9999), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) @with_altair_and_later @spec_state_test def test_random_inactivity_scores_random_participation(spec, state): - next_epoch_via_block(spec, state) - randomize_attestation_participation(spec, state, rng=Random(22222)) - randomize_inactivity_scores(spec, state, rng=Random(22222)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + randomize_attestation_participation, randomize_inactivity_scores, Random(22222), + ) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_random_inactivity_scores_random_participation_leaking(spec, state): + # Only randompize participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + randomize_previous_epoch_participation, randomize_inactivity_scores, Random(22222), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) @with_altair_and_later @spec_state_test def test_random_inactivity_scores_full_participation(spec, state): - next_epoch_via_block(spec, state) - set_full_participation(spec, state) - randomize_inactivity_scores(spec, state, rng=Random(33333)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + set_full_participation, randomize_inactivity_scores, Random(33333), + ) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_random_inactivity_scores_full_participation_leaking(spec, state): + # Only set full participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + set_full_participation_previous_epoch, randomize_inactivity_scores, Random(33333), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) diff --git a/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py b/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py index b6b362a8b..80ef91d0f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py @@ -112,7 +112,7 @@ def test_random_high_inactivity_scores_leaking(spec, state): @with_altair_and_later @spec_state_test -@leaking(epochs=5) -def test_random_high_inactivity_scores_leaking_5_epochs(spec, state): +@leaking(epochs=8) +def test_random_high_inactivity_scores_leaking_8_epochs(spec, state): randomize_inactivity_scores(spec, state, minimum=500000, maximum=5000000, rng=Random(9998)) yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(9998)) diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py index 0c85bd1e2..d87522bda 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py @@ -1,8 +1,9 @@ -import random +from random import Random + from eth2spec.test.helpers.state import ( state_transition_and_sign_block, next_epoch, - next_epoch_via_block, + set_full_participation_previous_epoch, ) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, @@ -15,12 +16,14 @@ from eth2spec.test.context import ( with_altair_and_later, spec_state_test, ) +from eth2spec.test.helpers.rewards import leaking +from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores -def run_sync_committee_sanity_test(spec, state, fraction_full=1.0): +def run_sync_committee_sanity_test(spec, state, fraction_full=1.0, rng=Random(454545)): all_pubkeys = [v.pubkey for v in state.validators] committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys] - participants = random.sample(committee, int(len(committee) * fraction_full)) + participants = rng.sample(committee, int(len(committee) * fraction_full)) yield 'pre', state @@ -51,7 +54,7 @@ def test_full_sync_committee_committee(spec, state): @spec_state_test def test_half_sync_committee_committee(spec, state): next_epoch(spec, state) - yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5) + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5, rng=Random(1212)) @with_altair_and_later @@ -70,7 +73,7 @@ def test_full_sync_committee_committee_genesis(spec, state): @with_altair_and_later @spec_state_test def test_half_sync_committee_committee_genesis(spec, state): - yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5) + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5, rng=Random(2323)) @with_altair_and_later @@ -81,11 +84,13 @@ def test_empty_sync_committee_committee_genesis(spec, state): @with_altair_and_later @spec_state_test -def test_inactivity_scores(spec, state): - for _ in range(spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2): - next_epoch_via_block(spec, state) - +@leaking() +def test_inactivity_scores_leaking(spec, state): assert spec.is_in_inactivity_leak(state) + + randomize_inactivity_scores(spec, state, rng=Random(5252)) + assert len(set(state.inactivity_scores)) > 1 + previous_inactivity_scores = state.inactivity_scores.copy() yield 'pre', state @@ -97,5 +102,36 @@ def test_inactivity_scores(spec, state): yield 'blocks', [signed_block] yield 'post', state + # No particiaption during a leak so all scores should increase for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): assert post == pre + spec.config.INACTIVITY_SCORE_BIAS + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_inactivity_scores_full_participation_leaking(spec, state): + randomize_inactivity_scores(spec, state, rng=Random(5252)) + assert len(set(state.inactivity_scores)) > 1 + + # Only set full participation for previous epoch to remain in leak + set_full_participation_previous_epoch(spec, state) + + previous_inactivity_scores = state.inactivity_scores.copy() + + yield 'pre', state + + # Block transition to next epoch + block = build_empty_block(spec, state, slot=state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + + assert spec.is_in_inactivity_leak(state) + + yield 'blocks', [signed_block] + yield 'post', state + + # Full particiaption during a leak so all scores should decrease by 1 + print(previous_inactivity_scores) + print(state.inactivity_scores) + for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): + assert post == pre - 1 diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index 5c28bfc24..29f9038a8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -3,3 +3,7 @@ from random import Random def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Random(4242)): state.inactivity_scores = [rng.randint(minimum, maximum) for _ in range(len(state.validators))] + + +def zero_inactivity_scores(spec, state, rng=None): + state.inactivity_scores = [0] * len(state.validators) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 5b5e419ba..d804fa4b9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -100,6 +100,13 @@ def randomize_epoch_participation(spec, state, epoch, rng): epoch_participation[index] = flags +def randomize_previous_epoch_participation(spec, state, rng=Random(8020)): + cached_prepare_state_with_attestations(spec, state) + randomize_epoch_participation(spec, state, spec.get_previous_epoch(state), rng) + if not is_post_altair(spec): + state.current_epoch_attestations = [] + + def randomize_attestation_participation(spec, state, rng=Random(8020)): cached_prepare_state_with_attestations(spec, state) randomize_epoch_participation(spec, state, spec.get_previous_epoch(state), rng) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 83b99f66d..874e47305 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -260,9 +260,9 @@ def run_get_inactivity_penalty_deltas(spec, state): def transition_state_to_leak(spec, state, epochs=None): if epochs is None: - # +1 to trigger inactivity_score transitions - epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 - assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + # +2 because finality delay is based on previous_epoch and must be more than `MIN_EPOCHS_TO_INACTIVITY_PENALTY` + epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2 + assert epochs > spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY for _ in range(epochs): next_epoch(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 05f0e9013..666023fec 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import expect_assertion_error +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 @@ -92,3 +92,44 @@ def state_transition_and_sign_block(spec, state, block, expect_fail=False): transition_unsigned_block(spec, state, block) block.state_root = state.hash_tree_root() return sign_block(spec, state, block) + + +# +# WARNING: The following functions can only be used post-altair due to the manipulation of participation flags directly +# + + +def _set_full_participation(spec, state, current=True, previous=True): + assert is_post_altair(spec) + + full_flags = spec.ParticipationFlags(0) + for flag_index in range(len(spec.PARTICIPATION_FLAG_WEIGHTS)): + full_flags = spec.add_flag(full_flags, flag_index) + + for index in range(len(state.validators)): + if current: + state.current_epoch_participation[index] = full_flags.copy() + if previous: + state.previous_epoch_participation[index] = full_flags.copy() + + +def set_full_participation(spec, state, rng=None): + _set_full_participation(spec, state) + + +def set_full_participation_previous_epoch(spec, state, rng=None): + _set_full_participation(spec, state, current=False, previous=True) + + +def _set_empty_participation(spec, state, current=True, previous=True): + assert is_post_altair(spec) + + for index in range(len(state.validators)): + if current: + state.current_epoch_participation[index] = spec.ParticipationFlags(0) + if previous: + state.previous_epoch_participation[index] = spec.ParticipationFlags(0) + + +def set_empty_participation(spec, state, rng=None): + _set_empty_participation(spec, state)