From 0ec1ec77b85088a5890a59062c28870da91bf091 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 18 May 2021 17:25:51 -0700 Subject: [PATCH 1/8] Expose attestation helper in pyspec tests --- .../eth2spec/test/helpers/attestations.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 8e8002041..9163b34e3 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -219,15 +219,14 @@ def add_attestations_to_state(spec, state, attestations, slot): spec.process_attestation(state, attestation) -def next_epoch_with_attestations(spec, +def next_slots_with_attestations(spec, state, + slot_count, fill_cur_epoch, fill_prev_epoch): - assert state.slot % spec.SLOTS_PER_EPOCH == 0 - post_state = state.copy() signed_blocks = [] - for _ in range(spec.SLOTS_PER_EPOCH): + for _ in range(slot_count): block = build_empty_block_for_next_slot(spec, post_state) if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 @@ -256,6 +255,19 @@ def next_epoch_with_attestations(spec, return state, signed_blocks, post_state +def next_epoch_with_attestations(spec, + state, + fill_cur_epoch, + fill_prev_epoch): + assert state.slot % spec.SLOTS_PER_EPOCH == 0 + + return next_slots_with_attestations(spec, + state, + spec.SLOTS_PER_EPOCH, + fill_cur_epoch, + fill_prev_epoch) + + def prepare_state_with_attestations(spec, state, participation_fn=None): """ Prepare state with attestations according to the ``participation_fn``. From 7eba1612cf2311d74d55c076b2bdaa27b70a0f32 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 18 May 2021 17:26:18 -0700 Subject: [PATCH 2/8] Add fork transition test that finalizes across fork boundary --- .../test/altair/transition/test_transition.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) 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 c3b03d663..fdfea131e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -2,6 +2,7 @@ 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.block import build_empty_block_for_next_slot, build_empty_block, sign_block +from eth2spec.test.helpers.attestations import next_slots_with_attestations def _state_transition_and_sign_block_at_slot(spec, state): @@ -242,3 +243,77 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr 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 From df742ea8af57b774cb0907c132d20af5bece29c8 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 19 May 2021 12:16:24 -0700 Subject: [PATCH 3/8] add fork finality test with random participation --- .../test/altair/transition/test_transition.py | 88 +++++++++++++++++++ .../eth2spec/test/helpers/attestations.py | 36 ++++++-- 2 files changed, 117 insertions(+), 7 deletions(-) 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) From 1dfca0e6773d0ec8b74ec1f87d6f292f2b67ff56 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 20 May 2021 11:40:25 -0700 Subject: [PATCH 4/8] Refactor block helper --- tests/core/pyspec/eth2spec/test/helpers/block.py | 3 ++- tests/core/pyspec/eth2spec/test/helpers/state.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index 64f406633..0dd9dc98e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -61,6 +61,7 @@ def transition_unsigned_block(spec, state, block): assert state.latest_block_header.slot < block.slot # There may not already be a block in this slot or past it. assert state.slot == block.slot # The block must be for this slot spec.process_block(state, block) + return block def apply_empty_block(spec, state, slot=None): @@ -68,7 +69,7 @@ def apply_empty_block(spec, state, slot=None): Transition via an empty block (on current slot, assuming no block has been applied yet). """ block = build_empty_block(spec, state, slot) - transition_unsigned_block(spec, state, block) + return transition_unsigned_block(spec, state, block) def build_empty_block(spec, state, slot=None): diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index d61df7610..85d1eed5e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -62,7 +62,7 @@ def next_epoch_via_block(spec, state): """ Transition to the start slot of the next epoch via a full block transition """ - apply_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) + return apply_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) def get_state_root(spec, state, slot) -> bytes: From 240209915cda10ae5b88819903a1c8c5d61b6401 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 20 May 2021 11:41:06 -0700 Subject: [PATCH 5/8] Refactor attestation helper --- .../eth2spec/test/helpers/attestations.py | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 40179150f..fe42a3949 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -219,6 +219,26 @@ def add_attestations_to_state(spec, state, attestations, slot): spec.process_attestation(state, attestation) +def _get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None, on_time=True): + 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): + def participants_filter(comm): + if participation_fn is None: + return comm + else: + return participation_fn(state.slot, index, comm) + # if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block + yield get_valid_attestation( + spec, + state, + slot_to_attest, + index=index, + signed=True, + on_time=on_time, + filter_participant_set=participants_filter + ) + + def next_slots_with_attestations(spec, state, slot_count, @@ -234,42 +254,26 @@ def next_slots_with_attestations(spec, block = build_empty_block_for_next_slot(spec, post_state) if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 - committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest)) 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, - filter_participant_set=participants_filter) - block.body.attestations.append(cur_attestation) - + attestations = _get_valid_attestation_at_slot( + post_state, + spec, + slot_to_attest, + participation_fn=participation_fn + ) + for attestation in attestations: + block.body.attestations.append(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): - 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) + attestations = _get_valid_attestation_at_slot( + post_state, + spec, + slot_to_attest, + on_time=False, + participation_fn=participation_fn + ) + for attestation in attestations: + block.body.attestations.append(attestation) signed_block = state_transition_and_sign_block(spec, post_state, block) signed_blocks.append(signed_block) From d04ecf16b1173571a844a26cf557999c043e5b33 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 20 May 2021 11:41:38 -0700 Subject: [PATCH 6/8] PR feedback and add two more test cases --- .../test/altair/transition/test_transition.py | 281 ++++++++++-------- 1 file changed, 155 insertions(+), 126 deletions(-) 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 From b9ad8bca00f0d26f509b197a3440a5fd679a6504 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 20 May 2021 12:07:46 -0700 Subject: [PATCH 7/8] Update tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py --- .../pyspec/eth2spec/test/altair/transition/test_transition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6c91e9882..5ac40cce0 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -371,7 +371,7 @@ def test_transition_with_random_half_participation(state, fork_epoch, spec, post rng = random.Random(2020) def _drop_random_half(_slot, _index, indices): - # still finalize, but drop some attestations + # drop enough attestations to not finalize committee_len = len(indices) assert committee_len >= 2 filter_len = committee_len // 2 From ba6d19308b9f305e700bd8fe29f217a91370d3a2 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 21 May 2021 13:44:35 -0600 Subject: [PATCH 8/8] Update tests/core/pyspec/eth2spec/test/helpers/attestations.py Co-authored-by: Hsiao-Wei Wang --- .../pyspec/eth2spec/test/helpers/attestations.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index fe42a3949..b55aff9e5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -287,11 +287,13 @@ def next_epoch_with_attestations(spec, fill_prev_epoch): assert state.slot % spec.SLOTS_PER_EPOCH == 0 - return next_slots_with_attestations(spec, - state, - spec.SLOTS_PER_EPOCH, - fill_cur_epoch, - fill_prev_epoch) + return next_slots_with_attestations( + spec, + state, + spec.SLOTS_PER_EPOCH, + fill_cur_epoch, + fill_prev_epoch, + ) def prepare_state_with_attestations(spec, state, participation_fn=None):