diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index fdfea131e..80faf6692 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -1,3 +1,4 @@ +import random from eth2spec.test.context import fork_transition_test from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot @@ -317,3 +318,90 @@ def test_transition_with_finality(state, fork_epoch, spec, post_spec, pre_tag, p yield "blocks", blocks yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) +def test_transition_with_random_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Transition from the initial ``state`` to the epoch after the ``fork_epoch``, + including attestations so as to produce finality through the fork boundary. + """ + rng = random.Random(1337) + + def _drop_random_quarter(_slot, _index, indices): + # still finalize, but drop some attestations + committee_len = len(indices) + assert committee_len >= 4 + one_quarter_len = committee_len // 4 + participant_count = committee_len - one_quarter_len + return rng.sample(indices, participant_count) + + yield "pre", state + + current_epoch = spec.get_current_epoch(state) + assert current_epoch < fork_epoch + assert current_epoch == spec.GENESIS_EPOCH + + # regular state transition until fork: + fill_cur_epoch = False + fill_prev_epoch = True + blocks = [] + for _ in range(current_epoch, fork_epoch - 1): + if current_epoch == spec.GENESIS_EPOCH: + fill_cur_epoch = True + fill_prev_epoch = False + + _, blocks_in_epoch, state = next_slots_with_attestations(spec, + state, + spec.SLOTS_PER_EPOCH, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=_drop_random_quarter) + blocks.extend([pre_tag(block) for block in blocks_in_epoch]) + if current_epoch == spec.GENESIS_EPOCH: + fill_cur_epoch = False + fill_prev_epoch = True + + _, blocks_in_epoch, state = next_slots_with_attestations(spec, + state, + spec.SLOTS_PER_EPOCH - 1, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=_drop_random_quarter) + blocks.extend([pre_tag(block) for block in blocks_in_epoch]) + assert spec.get_current_epoch(state) == fork_epoch - 1 + assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0 + + # irregular state transition to handle fork: + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + for _ in range(4): + _, blocks_in_epoch, state = next_slots_with_attestations(post_spec, + state, + post_spec.SLOTS_PER_EPOCH, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=_drop_random_quarter) + blocks.extend([post_tag(block) for block in blocks_in_epoch]) + + assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 + assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 4 + + assert state.current_justified_checkpoint.epoch == fork_epoch + 2 + assert state.finalized_checkpoint.epoch == fork_epoch + + assert len(blocks) == (fork_epoch + 4) * post_spec.SLOTS_PER_EPOCH + assert len(blocks) == len(set(blocks)) + + blocks_without_attestations = [block for block in blocks if len(block.message.body.attestations) == 0] + # A boundary condition of ``next_slots_with_attestations`` skips + # over the block with ``slot == 1``. + # And the fork upgrade helper currently does not construct any attestations. + assert len(blocks_without_attestations) == 2 + slots_without_attestations = [b.message.slot for b in blocks_without_attestations] + assert set(slots_without_attestations) == set([1, fork_epoch * spec.SLOTS_PER_EPOCH]) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 9163b34e3..40179150f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -223,7 +223,11 @@ def next_slots_with_attestations(spec, state, slot_count, fill_cur_epoch, - fill_prev_epoch): + fill_prev_epoch, + participation_fn=None): + """ + participation_fn: (slot, committee_index, committee_indices_set) -> participants_indices_set + """ post_state = state.copy() signed_blocks = [] for _ in range(slot_count): @@ -234,19 +238,37 @@ def next_slots_with_attestations(spec, if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): for index in range(committees_per_slot): # if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block + def participants_filter(comm): + if participation_fn is None: + return comm + else: + return participation_fn(post_state.slot, index, comm) - cur_attestation = get_valid_attestation( - spec, post_state, slot_to_attest, - index=index, signed=True, on_time=True - ) + cur_attestation = get_valid_attestation(spec, + post_state, + slot_to_attest, + index=index, + signed=True, + on_time=True, + filter_participant_set=participants_filter) block.body.attestations.append(cur_attestation) if fill_prev_epoch: slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest)) for index in range(committees_per_slot): - prev_attestation = get_valid_attestation( - spec, post_state, slot_to_attest, index=index, signed=True, on_time=False) + def participants_filter(comm): + if participation_fn is None: + return comm + else: + return participation_fn(post_state.slot, index, comm) + prev_attestation = get_valid_attestation(spec, + post_state, + slot_to_attest, + index=index, + signed=True, + on_time=False, + filter_participant_set=participants_filter) block.body.attestations.append(prev_attestation) signed_block = state_transition_and_sign_block(spec, post_state, block)