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 80faf6692..6c91e9882 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -1,7 +1,7 @@ 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 +from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_block from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block from eth2spec.test.helpers.attestations import next_slots_with_attestations @@ -66,7 +66,7 @@ def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True): spec.process_slots(state, state.slot + 1) assert state.slot % spec.SLOTS_PER_EPOCH == 0 - assert spec.compute_epoch_at_slot(state.slot) == fork_epoch + assert spec.get_current_epoch(state) == fork_epoch state = post_spec.upgrade_to_altair(state) @@ -110,7 +110,7 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 - assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1 + assert post_spec.get_current_epoch(state) == fork_epoch + 1 slots_with_blocks = [block.message.slot for block in blocks] assert len(set(slots_with_blocks)) == len(slots_with_blocks) @@ -150,7 +150,7 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 - assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1 + assert post_spec.get_current_epoch(state) == fork_epoch + 1 slots_with_blocks = [block.message.slot for block in blocks] assert len(set(slots_with_blocks)) == len(slots_with_blocks) @@ -193,7 +193,7 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 - assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1 + assert post_spec.get_current_epoch(state) == fork_epoch + 1 slots_with_blocks = [block.message.slot for block in blocks] assert len(set(slots_with_blocks)) == len(slots_with_blocks) @@ -236,7 +236,7 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 - assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1 + assert post_spec.get_current_epoch(state) == fork_epoch + 1 slots_with_blocks = [block.message.slot for block in blocks] assert len(slots_with_blocks) == 1 @@ -246,82 +246,101 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr yield "post", state +def _run_transition_test_with_attestations(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + participation_fn=None, + expect_finality=True): + yield "pre", state + + current_epoch = spec.get_current_epoch(state) + assert current_epoch < fork_epoch + assert current_epoch == spec.GENESIS_EPOCH + + # skip genesis epoch to avoid dealing with some edge cases... + block = next_epoch_via_block(spec, state) + + # regular state transition until fork: + fill_cur_epoch = False + fill_prev_epoch = True + blocks = [pre_tag(sign_block(spec, state, block))] + current_epoch = spec.get_current_epoch(state) + for _ in range(current_epoch, fork_epoch - 1): + _, blocks_in_epoch, state = next_slots_with_attestations( + spec, + state, + spec.SLOTS_PER_EPOCH, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=participation_fn, + ) + blocks.extend([pre_tag(block) for block in blocks_in_epoch]) + + _, blocks_in_epoch, state = next_slots_with_attestations( + spec, + state, + spec.SLOTS_PER_EPOCH - 1, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=participation_fn, + ) + 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=participation_fn, + ) + blocks.extend([post_tag(block) for block in blocks_in_epoch]) + + assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 + assert post_spec.get_current_epoch(state) == fork_epoch + 4 + + if expect_finality: + assert state.current_justified_checkpoint.epoch == fork_epoch + 2 + assert state.finalized_checkpoint.epoch == fork_epoch + else: + assert state.current_justified_checkpoint.epoch == spec.GENESIS_EPOCH + assert state.finalized_checkpoint.epoch == spec.GENESIS_EPOCH + + assert len(blocks) == (fork_epoch + 3) * post_spec.SLOTS_PER_EPOCH + 1 + assert len(blocks) == len(set(blocks)) + + blocks_without_attestations = [block for block in blocks if len(block.message.body.attestations) == 0] + assert len(blocks_without_attestations) == 2 + slots_without_attestations = [b.message.slot for b in blocks_without_attestations] + + assert set(slots_without_attestations) == set([spec.SLOTS_PER_EPOCH, fork_epoch * spec.SLOTS_PER_EPOCH]) + + yield "blocks", blocks + yield "post", state + + @fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) def test_transition_with_finality(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. """ - 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) - 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) - 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) - 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 + yield from _run_transition_test_with_attestations(state, fork_epoch, spec, post_spec, pre_tag, post_tag) @fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) -def test_transition_with_random_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag): +def test_transition_with_random_three_quarters_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. @@ -332,76 +351,86 @@ def test_transition_with_random_participation(state, fork_epoch, spec, post_spec # 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 + filter_len = committee_len // 4 + participant_count = committee_len - filter_len return rng.sample(indices, participant_count) + yield from _run_transition_test_with_attestations( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + participation_fn=_drop_random_quarter + ) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) +def test_transition_with_random_half_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + rng = random.Random(2020) + + def _drop_random_half(_slot, _index, indices): + # still finalize, but drop some attestations + committee_len = len(indices) + assert committee_len >= 2 + filter_len = committee_len // 2 + participant_count = committee_len - filter_len + return rng.sample(indices, participant_count) + + yield from _run_transition_test_with_attestations( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + participation_fn=_drop_random_half, + expect_finality=False + ) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Transition from the initial ``state`` to the ``fork_epoch`` with no attestations, + then transition forward with enough attestations to finalize the fork epoch. + """ yield "pre", state - current_epoch = spec.get_current_epoch(state) - assert current_epoch < fork_epoch - assert current_epoch == spec.GENESIS_EPOCH + assert spec.get_current_epoch(state) < fork_epoch # regular state transition until fork: - fill_cur_epoch = False - fill_prev_epoch = True + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 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 + blocks.extend([ + pre_tag(block) for block in + _state_transition_across_slots(spec, state, to_slot) + ]) # 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 + # continue regular state transition but add attestations + # for enough epochs to finalize the ``fork_epoch`` + block = next_epoch_via_block(post_spec, state) + blocks.append(post_tag(sign_block(post_spec, state, block))) 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_in_epoch, state = next_slots_with_attestations( + post_spec, + state, + post_spec.SLOTS_PER_EPOCH, + False, + True, + ) 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 post_spec.get_current_epoch(state) == fork_epoch + 5 - 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]) + assert state.current_justified_checkpoint.epoch == fork_epoch + 3 + assert state.finalized_checkpoint.epoch == fork_epoch + 1 yield "blocks", blocks yield "post", state