diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 12aa815ad..438e2c20c 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -1,12 +1,16 @@ import random from eth2spec.test.context import ( - MINIMAL, - fork_transition_test, + ForkMeta, + ALTAIR, with_presets, + with_fork_metas, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, + MINIMAL, ) -from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( - do_altair_fork, + do_fork, transition_until_fork, transition_to_next_epoch_and_append_blocks, ) @@ -21,7 +25,7 @@ from eth2spec.test.helpers.random import ( # Exit # -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) @with_presets([MINIMAL], reason="only test with enough validators such that at least one exited index is not in sync committee") def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, @@ -59,7 +63,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, # irregular state transition to handle fork: blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # ensure that some of the current sync committee members are exiting @@ -81,7 +85,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, fork_epoch, spec, @@ -117,7 +121,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, # irregular state transition to handle fork: blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # check post transition state @@ -127,9 +131,13 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, assert not post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) assert not post_spec.is_in_inactivity_leak(state) - # ensure that none of the current sync committee members are exited validators exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] - assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + some_sync_committee_exited = any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + if post_spec.fork == ALTAIR: + # in Altair fork, the sync committee members would be set with only active validators + assert not some_sync_committee_exited + else: + assert some_sync_committee_exited # continue regular state transition with new spec into next epoch transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) @@ -143,7 +151,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, # -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Create some deposits before the transition @@ -161,7 +169,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos # irregular state transition to handle fork: blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch @@ -171,7 +179,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_activation_at_fork_epoch(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Create some deposits before the transition @@ -191,7 +199,7 @@ def test_transition_with_activation_at_fork_epoch(state, fork_epoch, spec, post_ # irregular state transition to handle fork: blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py index 6cdac1661..338289597 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -1,13 +1,18 @@ -from eth2spec.test.context import fork_transition_test -from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.context import ( + ForkMeta, + with_fork_metas, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, +) from eth2spec.test.helpers.fork_transition import ( - do_altair_fork, + do_fork, transition_until_fork, transition_to_next_epoch_and_append_blocks, ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=7) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=7) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). @@ -22,7 +27,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr # irregular state transition to handle fork: blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # check post transition state @@ -35,7 +40,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=6) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=6) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). @@ -50,7 +55,7 @@ def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre # irregular state transition to handle fork: blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # check post transition state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py index e19c57fb1..121053186 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py @@ -1,8 +1,13 @@ from eth2spec.test.context import ( + ForkMeta, always_bls, - fork_transition_test, + with_fork_metas, + with_presets, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, + MINIMAL, ) -from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( OperationType, run_transition_with_operation, @@ -13,7 +18,7 @@ from eth2spec.test.helpers.fork_transition import ( # PROPOSER_SLASHING # -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) @always_bls def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -31,7 +36,7 @@ def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, s ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) @always_bls def test_transition_with_proposer_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -54,7 +59,7 @@ def test_transition_with_proposer_slashing_right_before_fork(state, fork_epoch, # -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) @always_bls def test_transition_with_attester_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -72,7 +77,7 @@ def test_transition_with_attester_slashing_right_after_fork(state, fork_epoch, s ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) @always_bls def test_transition_with_attester_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -95,7 +100,7 @@ def test_transition_with_attester_slashing_right_before_fork(state, fork_epoch, # -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_deposit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Create a deposit right *after* the transition @@ -112,7 +117,7 @@ def test_transition_with_deposit_right_after_fork(state, fork_epoch, spec, post_ ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_deposit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Create a deposit right *before* the transition @@ -134,11 +139,12 @@ def test_transition_with_deposit_right_before_fork(state, fork_epoch, spec, post # -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=66) for pre, post in ALL_PRE_POST_FORKS]) +@with_presets([MINIMAL], reason="too slow") def test_transition_with_voluntary_exit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Create a voluntary exit right *after* the transition. - fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. + fork_epoch=66 because minimal preset `SHARD_COMMITTEE_PERIOD` is 64 epochs. """ # Fast forward to the future epoch so that validator can do voluntary exit state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -155,11 +161,12 @@ def test_transition_with_voluntary_exit_right_after_fork(state, fork_epoch, spec ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=66) for pre, post in ALL_PRE_POST_FORKS]) +@with_presets([MINIMAL], reason="too slow") def test_transition_with_voluntary_exit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Create a voluntary exit right *before* the transition. - fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. + fork_epoch=66 because minimal preset `SHARD_COMMITTEE_PERIOD` is 64 epochs. """ # Fast forward to the future epoch so that validator can do voluntary exit state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 211a4fbfe..b3ba92321 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -1,12 +1,15 @@ import random from eth2spec.test.context import ( - MINIMAL, - fork_transition_test, + ForkMeta, + with_fork_metas, with_presets, ) -from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, + MINIMAL, +) from eth2spec.test.helpers.fork_transition import ( - do_altair_fork, + do_fork, transition_to_next_epoch_and_append_blocks, transition_until_fork, ) @@ -15,7 +18,7 @@ from eth2spec.test.helpers.random import ( ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=1) for pre, post in ALL_PRE_POST_FORKS]) @with_presets([MINIMAL], reason="only test with enough validators such that at least one exited index is not in sync committee") def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, @@ -45,7 +48,7 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, yield "pre", state # irregular state transition to handle fork: - state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) + state, _ = do_fork(state, spec, post_spec, fork_epoch, with_block=False) # ensure that some of the current sync committee members are slashed slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] 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 fe6248c5f..2ac8bfb48 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -1,12 +1,17 @@ import random -from eth2spec.test.context import fork_transition_test -from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.context import ( + ForkMeta, + with_fork_metas, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, +) from eth2spec.test.helpers.state import ( next_epoch_via_signed_block, ) from eth2spec.test.helpers.attestations import next_slots_with_attestations from eth2spec.test.helpers.fork_transition import ( - do_altair_fork, + do_fork, no_blocks, only_at, skip_slots, @@ -15,7 +20,7 @@ from eth2spec.test.helpers.fork_transition import ( ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, @@ -34,7 +39,7 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag ]) # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch @@ -51,7 +56,7 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, @@ -71,7 +76,7 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, ]) # irregular state transition to handle fork: - state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) + state, _ = do_fork(state, spec, post_spec, fork_epoch, with_block=False) # continue regular state transition with new spec into next epoch transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) @@ -88,7 +93,7 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, @@ -109,7 +114,7 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp ]) # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch @@ -127,7 +132,7 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, @@ -148,7 +153,7 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr ]) # irregular state transition to handle fork: - state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) + state, _ = do_fork(state, spec, post_spec, fork_epoch, with_block=False) # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot @@ -215,7 +220,7 @@ def _run_transition_test_with_attestations(state, 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) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch @@ -253,7 +258,7 @@ def _run_transition_test_with_attestations(state, yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) for pre, post in ALL_PRE_POST_FORKS]) 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``, @@ -262,7 +267,7 @@ def test_transition_with_finality(state, fork_epoch, spec, post_spec, pre_tag, p 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) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) for pre, post in ALL_PRE_POST_FORKS]) 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``, @@ -289,7 +294,7 @@ def test_transition_with_random_three_quarters_participation(state, fork_epoch, ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) for pre, post in ALL_PRE_POST_FORKS]) def test_transition_with_random_half_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag): rng = random.Random(2020) @@ -313,7 +318,7 @@ def test_transition_with_random_half_participation(state, fork_epoch, spec, post ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas([ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) for pre, post in ALL_PRE_POST_FORKS]) 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, @@ -332,7 +337,7 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe ]) # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition but add attestations diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 944351bfd..184c0d609 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -1,5 +1,7 @@ import pytest from copy import deepcopy +from dataclasses import dataclass + from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal from eth2spec.merge import mainnet as spec_merge_mainnet, minimal as spec_merge_minimal @@ -7,12 +9,16 @@ from eth2spec.utils import bls from .exceptions import SkippedTest from .helpers.constants import ( - SpecForkName, PresetBaseName, PHASE0, ALTAIR, MERGE, MINIMAL, MAINNET, ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, + ALL_FORK_UPGRADES, ) +from .helpers.typing import SpecForkName, PresetBaseName from .helpers.genesis import create_genesis_state -from .utils import vector_test, with_meta_tags, build_transition_test +from .utils import ( + vector_test, + with_meta_tags, +) from random import Random from typing import Any, Callable, Sequence, TypedDict, Protocol, Dict @@ -50,6 +56,13 @@ class SpecMerge(Spec): ... +@dataclass(frozen=True) +class ForkMeta: + pre_fork_name: str + post_fork_name: str + fork_epoch: int + + spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = { MINIMAL: { PHASE0: spec_phase0_minimal, @@ -86,7 +99,6 @@ _custom_state_cache_dict = LRU(size=10) def with_custom_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int]): def deco(fn): - def entry(*args, spec: Spec, phases: SpecForks, **kw): # make a key for the state, unique to the fork + config (incl preset choice) and balances/activations key = (spec.fork, spec.config.__hash__(), spec.__file__, balances_fn, threshold_fn) @@ -104,7 +116,7 @@ def with_custom_state(balances_fn: Callable[[Any], Sequence[int]], return deco -def default_activation_threshold(spec): +def default_activation_threshold(spec: Spec): """ Helper method to use the default balance activation threshold for state creation for tests. Usage: `@with_custom_state(threshold_fn=default_activation_threshold, ...)` @@ -112,7 +124,7 @@ def default_activation_threshold(spec): return spec.MAX_EFFECTIVE_BALANCE -def zero_activation_threshold(spec): +def zero_activation_threshold(spec: Spec): """ Helper method to use 0 gwei as the activation threshold for state creation for tests. Usage: `@with_custom_state(threshold_fn=zero_activation_threshold, ...)` @@ -120,7 +132,7 @@ def zero_activation_threshold(spec): return 0 -def default_balances(spec): +def default_balances(spec: Spec): """ Helper method to create a series of default balances. Usage: `@with_custom_state(balances_fn=default_balances, ...)` @@ -129,7 +141,7 @@ def default_balances(spec): return [spec.MAX_EFFECTIVE_BALANCE] * num_validators -def scaled_churn_balances(spec): +def scaled_churn_balances(spec: Spec): """ Helper method to create enough validators to scale the churn limit. (This is *firmly* over the churn limit -- thus the +2 instead of just +1) @@ -143,7 +155,7 @@ def scaled_churn_balances(spec): with_state = with_custom_state(default_balances, default_activation_threshold) -def low_balances(spec): +def low_balances(spec: Spec): """ Helper method to create a series of low balances. Usage: `@with_custom_state(balances_fn=low_balances, ...)` @@ -154,7 +166,7 @@ def low_balances(spec): return [low_balance] * num_validators -def misc_balances(spec): +def misc_balances(spec: Spec): """ Helper method to create a series of balances that includes some misc. balances. Usage: `@with_custom_state(balances_fn=misc_balances, ...)` @@ -166,7 +178,7 @@ def misc_balances(spec): return balances -def misc_balances_in_default_range_with_many_validators(spec): +def misc_balances_in_default_range_with_many_validators(spec: Spec): """ Helper method to create a series of balances that includes some misc. balances but none that are below the ``EJECTION_BALANCE``. @@ -182,7 +194,7 @@ def misc_balances_in_default_range_with_many_validators(spec): return balances -def low_single_balance(spec): +def low_single_balance(spec: Spec): """ Helper method to create a single of balance of 1 Gwei. Usage: `@with_custom_state(balances_fn=low_single_balance, ...)` @@ -190,7 +202,7 @@ def low_single_balance(spec): return [1] -def large_validator_set(spec): +def large_validator_set(spec: Spec): """ Helper method to create a large series of default balances. Usage: `@with_custom_state(balances_fn=default_balances, ...)` @@ -347,6 +359,66 @@ def with_all_phases_except(exclusion_phases): return decorator +def _get_preset_targets(kw): + preset_name = DEFAULT_TEST_PRESET + if 'preset' in kw: + preset_name = kw.pop('preset') + return spec_targets[preset_name] + + +def _get_run_phases(phases, kw): + """ + Return the fork names for the base `spec` in test cases + """ + if 'phase' in kw: + # Limit phases if one explicitly specified + phase = kw.pop('phase') + if phase not in phases: + dump_skipping_message(f"doesn't support this fork: {phase}") + return None + run_phases = [phase] + else: + # If pytest `--fork` flag is set, filter out the rest of the forks + run_phases = set(phases).intersection(DEFAULT_PYTEST_FORKS) + + return run_phases + + +def _get_available_phases(run_phases, other_phases): + """ + Return the available fork names for multi-phase tests + """ + available_phases = set(run_phases) + if other_phases is not None: + available_phases |= set(other_phases) + return available_phases + + +def _run_test_case_with_phases(fn, phases, other_phases, kw, args, is_fork_transition=False): + run_phases = _get_run_phases(phases, kw) + + if len(run_phases) == 0: + if not is_fork_transition: + dump_skipping_message("none of the recognized phases are executable, skipping test.") + return None + + available_phases = _get_available_phases(run_phases, other_phases) + + targets = _get_preset_targets(kw) + + # Populate all phases for multi-phase tests + phase_dir = {} + for phase in available_phases: + phase_dir[phase] = targets[phase] + + # Return is ignored whenever multiple phases are ran. + # This return is for test generators to emit python generators (yielding test vector outputs) + for phase in run_phases: + ret = fn(spec=targets[phase], phases=phase_dir, *args, **kw) + + return ret + + def with_phases(phases, other_phases=None): """ Decorator factory that returns a decorator that runs a test for the appropriate phases. @@ -354,49 +426,22 @@ def with_phases(phases, other_phases=None): """ def decorator(fn): def wrapper(*args, **kw): - run_phases = set(phases).intersection(DEFAULT_PYTEST_FORKS) - - # limit phases if one explicitly specified - if 'phase' in kw: - phase = kw.pop('phase') - if phase not in phases: - dump_skipping_message(f"doesn't support this fork: {phase}") - return None - run_phases = [phase] - - if PHASE0 not in run_phases and ALTAIR not in run_phases and MERGE not in run_phases: - dump_skipping_message("none of the recognized phases are executable, skipping test.") - return None - - available_phases = set(run_phases) - if other_phases is not None: - available_phases |= set(other_phases) - - preset_name = DEFAULT_TEST_PRESET - if 'preset' in kw: - preset_name = kw.pop('preset') - targets = spec_targets[preset_name] - - # Populate all phases for multi-phase tests - phase_dir = {} - if PHASE0 in available_phases: - phase_dir[PHASE0] = targets[PHASE0] - if ALTAIR in available_phases: - phase_dir[ALTAIR] = targets[ALTAIR] - if MERGE in available_phases: - phase_dir[MERGE] = targets[MERGE] - - # return is ignored whenever multiple phases are ran. - # This return is for test generators to emit python generators (yielding test vector outputs) - if PHASE0 in run_phases: - ret = fn(spec=targets[PHASE0], phases=phase_dir, *args, **kw) - if ALTAIR in run_phases: - ret = fn(spec=targets[ALTAIR], phases=phase_dir, *args, **kw) - if MERGE in run_phases: - ret = fn(spec=targets[MERGE], phases=phase_dir, *args, **kw) - - # TODO: merge, sharding, custody_game and das are not executable yet. - # Tests that specify these features will not run, and get ignored for these specific phases. + if 'fork_metas' in kw: + fork_metas = kw.pop('fork_metas') + if 'phase' in kw: + # When running test generator, it sets specific `phase` + phase = kw['phase'] + _phases = [phase] + _other_phases = [ALL_FORK_UPGRADES[phase]] + ret = _run_test_case_with_phases(fn, _phases, _other_phases, kw, args, is_fork_transition=True) + else: + # When running pytest, go through `fork_metas` instead of using `phases` + for fork_meta in fork_metas: + _phases = [fork_meta.pre_fork_name] + _other_phases = [fork_meta.post_fork_name] + ret = _run_test_case_with_phases(fn, _phases, _other_phases, kw, args, is_fork_transition=True) + else: + ret = _run_test_case_with_phases(fn, phases, other_phases, kw, args) return ret return wrapper return decorator @@ -481,10 +526,25 @@ def only_generator(reason): return _decorator -def fork_transition_test(pre_fork_name, post_fork_name, fork_epoch=None): +# +# Fork transition state tests +# + + +def set_fork_metas(fork_metas: Sequence[ForkMeta]): + def decorator(fn): + def wrapper(*args, **kwargs): + return fn(*args, fork_metas=fork_metas, **kwargs) + return wrapper + return decorator + + +def with_fork_metas(fork_metas: Sequence[ForkMeta]): """ - A decorator to construct a "transition" test from one fork of the eth2 spec - to another. + A decorator to construct a "transition" test from one fork to another. + + Decorator takes a list of `ForkMeta` and each item defines `pre_fork_name`, + `post_fork_name`, and `fork_epoch`. Decorator assumes a transition from the `pre_fork_name` fork to the `post_fork_name` fork. The user can supply a `fork_epoch` at which the @@ -502,15 +562,65 @@ def fork_transition_test(pre_fork_name, post_fork_name, fork_epoch=None): `post_tag`: a function to tag data as belonging to `post_fork_name` fork. Used to discriminate data during consumption of the generated spec tests. """ - def _wrapper(fn): - @with_phases([pre_fork_name], other_phases=[post_fork_name]) - @spec_test - @with_state - def _adapter(*args, **kwargs): - wrapped = build_transition_test(fn, - pre_fork_name, - post_fork_name, - fork_epoch=fork_epoch) - return wrapped(*args, **kwargs) - return _adapter - return _wrapper + run_yield_fork_meta = yield_fork_meta(fork_metas) + run_with_phases = with_phases(ALL_PHASES) + run_set_fork_metas = set_fork_metas(fork_metas) + + def decorator(fn): + return run_set_fork_metas(run_with_phases(spec_test(with_state(run_yield_fork_meta(fn))))) + return decorator + + +def yield_fork_meta(fork_metas: Sequence[ForkMeta]): + """ + Yield meta fields to `meta.yaml` and pass post spec and meta fields to `fn`. + """ + def decorator(fn): + def wrapper(*args, **kw): + phases = kw.pop('phases') + spec = kw["spec"] + try: + fork_meta = next(filter(lambda m: m.pre_fork_name == spec.fork, fork_metas)) + except StopIteration: + dump_skipping_message(f"doesn't support this fork: {spec.fork}") + + post_spec = phases[fork_meta.post_fork_name] + + # Reset counter + pre_fork_counter = 0 + + def pre_tag(obj): + nonlocal pre_fork_counter + pre_fork_counter += 1 + return obj + + def post_tag(obj): + return obj + + yield "post_fork", "meta", fork_meta.post_fork_name + + has_fork_epoch = False + if fork_meta.fork_epoch: + kw["fork_epoch"] = fork_meta.fork_epoch + has_fork_epoch = True + yield "fork_epoch", "meta", fork_meta.fork_epoch + + result = fn( + *args, + post_spec=post_spec, + pre_tag=pre_tag, + post_tag=post_tag, + **kw, + ) + if result is not None: + for part in result: + if part[0] == "fork_epoch": + has_fork_epoch = True + yield part + assert has_fork_epoch + + if pre_fork_counter > 0: + yield "fork_block", "meta", pre_fork_counter - 1 + + return wrapper + return decorator diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index 5ab847327..bb8f49cbc 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -21,6 +21,14 @@ TESTGEN_FORKS = (PHASE0, ALTAIR, MERGE) FORKS_BEFORE_ALTAIR = (PHASE0,) FORKS_BEFORE_MERGE = (PHASE0, ALTAIR) +ALL_FORK_UPGRADES = { + # pre_fork_name: post_fork_name + PHASE0: ALTAIR, + ALTAIR: MERGE, +} +ALL_PRE_POST_FORKS = ALL_FORK_UPGRADES.items() +AFTER_MERGE_UPGRADES = {key: value for key, value in ALL_FORK_UPGRADES.items() if key not in FORKS_BEFORE_ALTAIR} +AFTER_MERGE_PRE_POST_FORKS = AFTER_MERGE_UPGRADES.items() # # Config diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 310c4a8d2..853863e51 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -9,6 +9,10 @@ from eth2spec.test.helpers.block import ( build_empty_block, sign_block, ) +from eth2spec.test.helpers.constants import ( + ALTAIR, + MERGE, +) from eth2spec.test.helpers.deposits import ( prepare_state_and_deposit, ) @@ -133,17 +137,25 @@ def state_transition_across_slots_with_ignoring_proposers(spec, next_slot(spec, state) -def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=None): +def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=None): spec.process_slots(state, state.slot + 1) assert state.slot % spec.SLOTS_PER_EPOCH == 0 assert spec.get_current_epoch(state) == fork_epoch - state = post_spec.upgrade_to_altair(state) + if post_spec.fork == ALTAIR: + state = post_spec.upgrade_to_altair(state) + elif post_spec.fork == MERGE: + state = post_spec.upgrade_to_merge(state) assert state.fork.epoch == fork_epoch - assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION - assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION + + if post_spec.fork == ALTAIR: + assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION + assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION + elif post_spec.fork == MERGE: + assert state.fork.previous_version == post_spec.config.ALTAIR_FORK_VERSION + assert state.fork.current_version == post_spec.config.MERGE_FORK_VERSION if with_block: return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict) @@ -280,7 +292,7 @@ def run_transition_with_operation(state, # irregular state transition to handle fork: _operation_at_slot = operation_dict if is_at_fork else None - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot) + state, block = do_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot) blocks.append(post_tag(block)) if is_at_fork: diff --git a/tests/core/pyspec/eth2spec/test/merge/transition/__init__.py b/tests/core/pyspec/eth2spec/test/merge/transition/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/merge/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/merge/transition/test_transition.py new file mode 100644 index 000000000..d488d81dc --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/merge/transition/test_transition.py @@ -0,0 +1,35 @@ +from eth2spec.test.context import ( + ForkMeta, + with_fork_metas, +) +from eth2spec.test.helpers.constants import ( + AFTER_MERGE_PRE_POST_FORKS, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + transition_to_next_epoch_and_append_blocks, + transition_until_fork, +) + + +@with_fork_metas([ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) for pre, post in AFTER_MERGE_PRE_POST_FORKS +]) +def test_sample_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + transition_until_fork(spec, state, fork_epoch) + + # check pre state + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/utils/__init__.py b/tests/core/pyspec/eth2spec/test/utils/__init__.py index f6b2a8a44..abd79f9ed 100644 --- a/tests/core/pyspec/eth2spec/test/utils/__init__.py +++ b/tests/core/pyspec/eth2spec/test/utils/__init__.py @@ -1,12 +1,10 @@ from .utils import ( vector_test, with_meta_tags, - build_transition_test, ) __all__ = [ # avoid "unused import" lint error "vector_test", "with_meta_tags", - "build_transition_test", ] diff --git a/tests/core/pyspec/eth2spec/test/utils/utils.py b/tests/core/pyspec/eth2spec/test/utils/utils.py index 61fc75040..bad6c867b 100644 --- a/tests/core/pyspec/eth2spec/test/utils/utils.py +++ b/tests/core/pyspec/eth2spec/test/utils/utils.py @@ -1,4 +1,3 @@ -import inspect from typing import Dict, Any from eth2spec.utils.ssz.ssz_typing import View from eth2spec.utils.ssz.ssz_impl import serialize @@ -94,50 +93,3 @@ def with_meta_tags(tags: Dict[str, Any]): yield k, 'meta', v return entry return runner - - -def build_transition_test(fn, pre_fork_name, post_fork_name, fork_epoch=None): - """ - Handles the inner plumbing to generate `transition_test`s. - See that decorator in `context.py` for more information. - """ - def _adapter(*args, **kwargs): - post_spec = kwargs["phases"][post_fork_name] - - pre_fork_counter = 0 - - def pre_tag(obj): - nonlocal pre_fork_counter - pre_fork_counter += 1 - return obj - - def post_tag(obj): - return obj - - yield "post_fork", "meta", post_fork_name - - has_fork_epoch = False - if fork_epoch: - kwargs["fork_epoch"] = fork_epoch - has_fork_epoch = True - yield "fork_epoch", "meta", fork_epoch - - # massage args to handle an optional custom state using - # `with_custom_state` decorator - expected_args = inspect.getfullargspec(fn) - if "phases" not in expected_args.kwonlyargs: - kwargs.pop("phases", None) - - for part in fn(*args, - post_spec=post_spec, - pre_tag=pre_tag, - post_tag=post_tag, - **kwargs): - if part[0] == "fork_epoch": - has_fork_epoch = True - yield part - assert has_fork_epoch - - if pre_fork_counter > 0: - yield "fork_block", "meta", pre_fork_counter - 1 - return _adapter diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index a850a7f45..d6195de68 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -1,6 +1,14 @@ from typing import Iterable -from eth2spec.test.helpers.constants import ALTAIR, MINIMAL, MAINNET, PHASE0 +from eth2spec.test.helpers.constants import ( + MINIMAL, + MAINNET, + ALL_PRE_POST_FORKS, +) +from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing +from eth2spec.gen_helpers.gen_from_tests.gen import ( + generate_from_tests, +) from eth2spec.test.altair.transition import ( test_transition as test_altair_transition, test_activations_and_exits as test_altair_activations_and_exits, @@ -8,9 +16,9 @@ from eth2spec.test.altair.transition import ( test_slashing as test_altair_slashing, test_operations as test_altair_operations, ) - -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests +from eth2spec.test.merge.transition import ( + test_transition as test_merge_transition, +) def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_name: str) -> gen_typing.TestProvider: @@ -31,18 +39,21 @@ def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_n return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) -TRANSITION_TESTS = ( - (PHASE0, ALTAIR, test_altair_transition), - (PHASE0, ALTAIR, test_altair_activations_and_exits), - (PHASE0, ALTAIR, test_altair_leaking), - (PHASE0, ALTAIR, test_altair_slashing), - (PHASE0, ALTAIR, test_altair_operations), -) - - if __name__ == "__main__": - for pre_fork, post_fork, transition_test_module in TRANSITION_TESTS: - gen_runner.run_generator("transition", [ - create_provider(transition_test_module, MINIMAL, pre_fork, post_fork), - create_provider(transition_test_module, MAINNET, pre_fork, post_fork), - ]) + altair_tests = ( + test_altair_transition, + test_altair_activations_and_exits, + test_altair_leaking, + test_altair_slashing, + test_altair_operations, + ) + merge_tests = ( + test_merge_transition, + ) + all_tests = altair_tests + merge_tests + for transition_test_module in all_tests: + for pre_fork, post_fork in ALL_PRE_POST_FORKS: + gen_runner.run_generator("transition", [ + create_provider(transition_test_module, MINIMAL, pre_fork, post_fork), + create_provider(transition_test_module, MAINNET, pre_fork, post_fork), + ])