extend inactivity updates tests

This commit is contained in:
Danny Ryan 2021-07-07 12:17:39 -06:00
parent 153e1b34dd
commit 48382ce09c
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
7 changed files with 242 additions and 49 deletions

View File

@ -1,26 +1,20 @@
from random import Random from random import Random
from eth2spec.test.context import spec_state_test, with_altair_and_later 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 ( from eth2spec.test.helpers.state import (
next_epoch_via_block, next_epoch_via_block,
set_full_participation, set_full_participation_previous_epoch,
set_empty_participation,
) )
from eth2spec.test.helpers.epoch_processing import ( from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with run_epoch_processing_with
) )
from eth2spec.test.helpers.random import ( from eth2spec.test.helpers.random import (
randomize_attestation_participation, randomize_attestation_participation,
randomize_previous_epoch_participation,
) )
from eth2spec.test.helpers.rewards import leaking
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
def run_process_inactivity_updates(spec, state): def run_process_inactivity_updates(spec, state):
@ -33,58 +27,169 @@ def test_genesis(spec, state):
yield from run_process_inactivity_updates(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 # Genesis epoch processing is skipped
# Thus all of following tests all go past genesis epoch to test core functionality # 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 @with_altair_and_later
@spec_state_test @spec_state_test
def test_all_zero_inactivity_scores_empty_participation(spec, state): def test_all_zero_inactivity_scores_empty_participation(spec, state):
next_epoch_via_block(spec, state) yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores)
state.inactivity_scores = [0] * len(state.validators) assert set(state.inactivity_scores) == set([0])
yield from run_process_inactivity_updates(spec, state)
@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 @with_altair_and_later
@spec_state_test @spec_state_test
def test_all_zero_inactivity_scores_random_participation(spec, state): def test_all_zero_inactivity_scores_random_participation(spec, state):
next_epoch_via_block(spec, state) yield from run_inactivity_scores_test(
state.inactivity_scores = [0] * len(state.validators) spec, state,
randomize_attestation_participation(spec, state, rng=Random(5555)) randomize_attestation_participation, zero_inactivity_scores, rng=Random(5555),
yield from run_process_inactivity_updates(spec, state) )
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 @with_altair_and_later
@spec_state_test @spec_state_test
def test_all_zero_inactivity_scores_full_participation(spec, state): def test_all_zero_inactivity_scores_full_participation(spec, state):
next_epoch_via_block(spec, state) yield from run_inactivity_scores_test(
set_full_participation(spec, state) spec, state,
state.inactivity_scores = [0] * len(state.validators) set_full_participation, zero_inactivity_scores,
yield from run_process_inactivity_updates(spec, state) )
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 @with_altair_and_later
@spec_state_test @spec_state_test
def test_random_inactivity_scores_empty_participation(spec, state): def test_random_inactivity_scores_empty_participation(spec, state):
next_epoch_via_block(spec, state) yield from run_inactivity_scores_test(
randomize_inactivity_scores(spec, state, rng=Random(9999)) spec, state,
yield from run_process_inactivity_updates(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 @with_altair_and_later
@spec_state_test @spec_state_test
def test_random_inactivity_scores_random_participation(spec, state): def test_random_inactivity_scores_random_participation(spec, state):
next_epoch_via_block(spec, state) yield from run_inactivity_scores_test(
randomize_attestation_participation(spec, state, rng=Random(22222)) spec, state,
randomize_inactivity_scores(spec, state, rng=Random(22222)) randomize_attestation_participation, randomize_inactivity_scores, Random(22222),
yield from run_process_inactivity_updates(spec, state) )
@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 @with_altair_and_later
@spec_state_test @spec_state_test
def test_random_inactivity_scores_full_participation(spec, state): def test_random_inactivity_scores_full_participation(spec, state):
next_epoch_via_block(spec, state) yield from run_inactivity_scores_test(
set_full_participation(spec, state) spec, state,
randomize_inactivity_scores(spec, state, rng=Random(33333)) set_full_participation, randomize_inactivity_scores, Random(33333),
yield from run_process_inactivity_updates(spec, state) )
@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)

View File

@ -112,7 +112,7 @@ def test_random_high_inactivity_scores_leaking(spec, state):
@with_altair_and_later @with_altair_and_later
@spec_state_test @spec_state_test
@leaking(epochs=5) @leaking(epochs=8)
def test_random_high_inactivity_scores_leaking_5_epochs(spec, state): def test_random_high_inactivity_scores_leaking_8_epochs(spec, state):
randomize_inactivity_scores(spec, state, minimum=500000, maximum=5000000, rng=Random(9998)) 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)) yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(9998))

View File

@ -1,8 +1,9 @@
import random from random import Random
from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.state import (
state_transition_and_sign_block, state_transition_and_sign_block,
next_epoch, next_epoch,
next_epoch_via_block, set_full_participation_previous_epoch,
) )
from eth2spec.test.helpers.block import ( from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot, build_empty_block_for_next_slot,
@ -15,12 +16,14 @@ from eth2spec.test.context import (
with_altair_and_later, with_altair_and_later,
spec_state_test, 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] all_pubkeys = [v.pubkey for v in state.validators]
committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys] 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 yield 'pre', state
@ -51,7 +54,7 @@ def test_full_sync_committee_committee(spec, state):
@spec_state_test @spec_state_test
def test_half_sync_committee_committee(spec, state): def test_half_sync_committee_committee(spec, state):
next_epoch(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 @with_altair_and_later
@ -70,7 +73,7 @@ def test_full_sync_committee_committee_genesis(spec, state):
@with_altair_and_later @with_altair_and_later
@spec_state_test @spec_state_test
def test_half_sync_committee_committee_genesis(spec, state): 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 @with_altair_and_later
@ -81,11 +84,13 @@ def test_empty_sync_committee_committee_genesis(spec, state):
@with_altair_and_later @with_altair_and_later
@spec_state_test @spec_state_test
def test_inactivity_scores(spec, state): @leaking()
for _ in range(spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2): def test_inactivity_scores_leaking(spec, state):
next_epoch_via_block(spec, state)
assert spec.is_in_inactivity_leak(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() previous_inactivity_scores = state.inactivity_scores.copy()
yield 'pre', state yield 'pre', state
@ -97,5 +102,36 @@ def test_inactivity_scores(spec, state):
yield 'blocks', [signed_block] yield 'blocks', [signed_block]
yield 'post', state yield 'post', state
# No particiaption during a leak so all scores should increase
for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): for pre, post in zip(previous_inactivity_scores, state.inactivity_scores):
assert post == pre + spec.config.INACTIVITY_SCORE_BIAS 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

View File

@ -3,3 +3,7 @@ from random import Random
def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Random(4242)): 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))] 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)

View File

@ -100,6 +100,13 @@ def randomize_epoch_participation(spec, state, epoch, rng):
epoch_participation[index] = flags 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)): def randomize_attestation_participation(spec, state, rng=Random(8020)):
cached_prepare_state_with_attestations(spec, state) cached_prepare_state_with_attestations(spec, state)
randomize_epoch_participation(spec, state, spec.get_previous_epoch(state), rng) randomize_epoch_participation(spec, state, spec.get_previous_epoch(state), rng)

View File

@ -260,9 +260,9 @@ def run_get_inactivity_penalty_deltas(spec, state):
def transition_state_to_leak(spec, state, epochs=None): def transition_state_to_leak(spec, state, epochs=None):
if epochs is None: if epochs is None:
# +1 to trigger inactivity_score transitions # +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 + 1 epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2
assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY assert epochs > spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY
for _ in range(epochs): for _ in range(epochs):
next_epoch(spec, state) next_epoch(spec, state)

View File

@ -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 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) transition_unsigned_block(spec, state, block)
block.state_root = state.hash_tree_root() block.state_root = state.hash_tree_root()
return sign_block(spec, state, block) 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)