eth2.0-specs/tests/core/pyspec/eth2spec/test/context.py

464 lines
16 KiB
Python
Raw Normal View History

import pytest
2021-05-07 03:30:45 +00:00
from copy import deepcopy
2021-05-18 11:59:26 +00:00
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
2019-05-13 21:15:02 +00:00
from eth2spec.utils import bls
from .exceptions import SkippedTest
from .helpers.constants import (
2021-05-18 11:59:26 +00:00
SpecForkName, PresetBaseName,
PHASE0, ALTAIR, MERGE, MINIMAL, MAINNET,
2021-05-04 22:18:01 +00:00
ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE,
)
from .helpers.genesis import create_genesis_state
2021-04-28 00:24:15 +00:00
from .utils import vector_test, with_meta_tags, build_transition_test
2019-05-06 15:10:43 +00:00
from random import Random
2021-05-18 11:59:26 +00:00
from typing import Any, Callable, Sequence, TypedDict, Protocol, Dict
2019-05-31 08:41:39 +00:00
from lru import LRU
# TODO: currently phases are defined as python modules.
# It would be better if they would be more well-defined interfaces for stronger typing.
class Spec(Protocol):
version: str
class SpecPhase0(Spec):
...
2021-03-15 15:53:13 +00:00
class SpecAltair(Spec):
executable light client patch: beacon-chain.md (#2141) * Bump remerkleable to 0.1.18 * Disable `sync-protocol.md` for now. Make linter pass * Enable lightclient tests * Use *new* `optional_fast_aggregate_verify` * Fix ToC and codespell * Do not run phase1 tests with Lightclient patch * Fix the Eth1Data casting bug. Add a workaround. * Fix `run_on_attestation` testing helper * Revert * Rename `optional_fast_aggregate_verify` to `eth2_fast_aggregate_verify` * Apply Proto's suggestion * Apply Danny's suggestion * Fixing tests * Fix after rebasing * Rename `LIGHTCLIENT` -> `LIGHTCLIENT_PATCH` * New doctoc * Add lightclient patch configs * fix gitignore light client patch generator output * Upgrade state for light client patch * Add `lightclient-fork.md` to deal the fork boundary and fix `process_block_header` * Misc cleanups 1) Add a summary note for every function that is changed. 2) Avoid changing `process_block` (instead only change `process_block_header`). 3) Rename `G2_INFINITY_POINT_SIG` to `G2_POINT_AT_INFINITY` to avoid `SIG` contraction. 4) Misc cleanups * Update block.py * Update beacon-chain.md * Fix typo "minimal" -> "mainnet" Co-authored-by: Marin Petrunić <mpetrunic@users.noreply.github.com> * Use the new `BeaconBlockHeader` instead of phase 0 version * Update config files * Move `sync_committee_bits` and `sync_committee_signature` back to `BeaconBlockBody` Co-authored-by: protolambda <proto@protolambda.com> Co-authored-by: Justin <drakefjustin@gmail.com> Co-authored-by: Marin Petrunić <mpetrunic@users.noreply.github.com>
2020-12-15 05:18:20 +00:00
...
2021-04-05 14:59:09 +00:00
class SpecMerge(Spec):
...
2021-05-18 11:59:26 +00:00
spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = {
MINIMAL: {
PHASE0: spec_phase0_minimal,
ALTAIR: spec_altair_minimal,
MERGE: spec_merge_minimal,
},
MAINNET: {
PHASE0: spec_phase0_mainnet,
ALTAIR: spec_altair_mainnet,
MERGE: spec_merge_mainnet,
},
}
class SpecForks(TypedDict, total=False):
PHASE0: SpecPhase0
2021-03-15 15:53:13 +00:00
ALTAIR: SpecAltair
2021-04-05 14:59:09 +00:00
MERGE: SpecMerge
def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int],
2021-03-09 23:18:30 +00:00
spec: Spec, phases: SpecForks):
phase = phases[spec.fork]
balances = balances_fn(phase)
activation_threshold = threshold_fn(phase)
state = create_genesis_state(spec=phase, validator_balances=balances,
activation_threshold=activation_threshold)
return state
_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
2021-05-18 11:59:26 +00:00
key = (spec.fork, spec.config.PRESET_BASE, spec.__file__, balances_fn, threshold_fn)
global _custom_state_cache_dict
if key not in _custom_state_cache_dict:
2021-03-09 23:18:30 +00:00
state = _prepare_state(balances_fn, threshold_fn, spec, phases)
_custom_state_cache_dict[key] = state.get_backing()
2020-05-19 01:42:58 +00:00
# Take an entry out of the LRU.
# No copy is necessary, as we wrap the immutable backing with a new view.
state = spec.BeaconState(backing=_custom_state_cache_dict[key])
kw['state'] = state
return fn(*args, spec=spec, phases=phases, **kw)
return entry
return deco
def default_activation_threshold(spec):
2019-10-24 07:31:43 +00:00
"""
Helper method to use the default balance activation threshold for state creation for tests.
Usage: `@with_custom_state(threshold_fn=default_activation_threshold, ...)`
"""
return spec.MAX_EFFECTIVE_BALANCE
def zero_activation_threshold(spec):
"""
2020-03-18 15:55:09 +00:00
Helper method to use 0 gwei as the activation threshold for state creation for tests.
Usage: `@with_custom_state(threshold_fn=zero_activation_threshold, ...)`
"""
return 0
def default_balances(spec):
2019-10-24 07:31:43 +00:00
"""
Helper method to create a series of default balances.
Usage: `@with_custom_state(balances_fn=default_balances, ...)`
"""
2020-02-20 19:34:37 +00:00
num_validators = spec.SLOTS_PER_EPOCH * 8
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
with_state = with_custom_state(default_balances, default_activation_threshold)
def low_balances(spec):
"""
2019-10-24 07:31:43 +00:00
Helper method to create a series of low balances.
Usage: `@with_custom_state(balances_fn=low_balances, ...)`
"""
num_validators = spec.SLOTS_PER_EPOCH * 8
# Technically the balances cannot be this low starting from genesis, but it is useful for testing
low_balance = 18 * 10 ** 9
return [low_balance] * num_validators
def misc_balances(spec):
"""
Helper method to create a series of balances that includes some misc. balances.
2019-10-24 07:31:43 +00:00
Usage: `@with_custom_state(balances_fn=misc_balances, ...)`
"""
num_validators = spec.SLOTS_PER_EPOCH * 8
balances = [spec.MAX_EFFECTIVE_BALANCE * 2 * i // num_validators for i in range(num_validators)]
rng = Random(1234)
rng.shuffle(balances)
return balances
2019-05-06 15:10:43 +00:00
def low_single_balance(spec):
"""
Helper method to create a single of balance of 1 Gwei.
Usage: `@with_custom_state(balances_fn=low_single_balance, ...)`
"""
return [1]
2019-05-06 15:10:43 +00:00
def large_validator_set(spec):
"""
Helper method to create a large series of default balances.
Usage: `@with_custom_state(balances_fn=default_balances, ...)`
"""
num_validators = 2 * spec.SLOTS_PER_EPOCH * spec.MAX_COMMITTEES_PER_SLOT * spec.TARGET_COMMITTEE_SIZE
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
def single_phase(fn):
"""
Decorator that filters out the phases data.
most state tests only focus on behavior of a single phase (the "spec").
This decorator is applied as part of spec_state_test(fn).
"""
def entry(*args, **kw):
if 'phases' in kw:
kw.pop('phases')
return fn(*args, **kw)
return entry
# BLS is turned on by default, it can be disabled in tests by overriding this, or using `--disable-bls`.
# *This is for performance purposes during TESTING, DO NOT DISABLE IN PRODUCTION*.
2019-05-21 19:51:28 +00:00
# The runner of the test can indicate the preferred setting (test generators prefer BLS to be ON).
# - Some tests are marked as BLS-requiring, and ignore this setting.
# (tests that express differences caused by BLS, e.g. invalid signatures being rejected)
# - Some other tests are marked as BLS-ignoring, and ignore this setting.
# (tests that are heavily performance impacted / require unsigned state transitions)
# - Most tests respect the BLS setting.
DEFAULT_BLS_ACTIVE = True
2019-05-21 19:51:28 +00:00
is_pytest = True
def dump_skipping_message(reason: str) -> None:
message = f"[Skipped test] {reason}"
if is_pytest:
pytest.skip(message)
else:
raise SkippedTest(message)
def spec_test(fn):
# Bls switch must be wrapped by vector_test,
# to fully go through the yielded bls switch data, before setting back the BLS setting.
# A test may apply BLS overrides such as @always_bls,
# but if it yields data (n.b. @always_bls yields the bls setting), it should be wrapped by this decorator.
# This is why @alway_bls has its own bls switch, since the override is beyond the reach of the outer switch.
return vector_test()(bls_switch(fn))
2019-06-22 04:02:03 +00:00
# shorthand for decorating @spectest() @with_state @single_phase
2019-05-06 15:10:43 +00:00
def spec_state_test(fn):
return spec_test(with_state(single_phase(fn)))
def expect_assertion_error(fn):
2019-05-11 15:51:02 +00:00
bad = False
try:
fn()
2019-05-11 15:51:02 +00:00
bad = True
except AssertionError:
pass
except IndexError:
# Index errors are special; the spec is not explicit on bound checking, an IndexError is like a failed assert.
pass
2019-05-11 15:51:02 +00:00
if bad:
raise AssertionError('expected an assertion error, but got none.')
2019-05-13 21:15:02 +00:00
2019-05-20 17:38:18 +00:00
def never_bls(fn):
"""
Decorator to apply on ``bls_switch`` decorator to force BLS de-activation. Useful to mark tests as BLS-ignorant.
This decorator may only be applied to yielding spec test functions, and should be wrapped by vector_test,
as the yielding needs to complete before setting back the BLS setting.
2019-05-20 17:38:18 +00:00
"""
def entry(*args, **kw):
# override bls setting
kw['bls_active'] = False
return bls_switch(fn)(*args, **kw)
return with_meta_tags({'bls_setting': 2})(entry)
2019-05-20 17:38:18 +00:00
2019-05-13 21:15:02 +00:00
def always_bls(fn):
"""
Decorator to apply on ``bls_switch`` decorator to force BLS activation. Useful to mark tests as BLS-dependent.
This decorator may only be applied to yielding spec test functions, and should be wrapped by vector_test,
as the yielding needs to complete before setting back the BLS setting.
2019-05-13 21:15:02 +00:00
"""
def entry(*args, **kw):
# override bls setting
kw['bls_active'] = True
return bls_switch(fn)(*args, **kw)
return with_meta_tags({'bls_setting': 1})(entry)
2019-05-13 21:15:02 +00:00
def bls_switch(fn):
"""
Decorator to make a function execute with BLS ON, or BLS off.
Based on an optional bool argument ``bls_active``, passed to the function at runtime.
This decorator may only be applied to yielding spec test functions, and should be wrapped by vector_test,
as the yielding needs to complete before setting back the BLS setting.
2019-05-13 21:15:02 +00:00
"""
def entry(*args, **kw):
old_state = bls.bls_active
2019-05-21 19:51:28 +00:00
bls.bls_active = kw.pop('bls_active', DEFAULT_BLS_ACTIVE)
res = fn(*args, **kw)
if res is not None:
yield from res
2019-05-13 21:15:02 +00:00
bls.bls_active = old_state
return entry
2019-05-30 20:57:18 +00:00
def disable_process_reveal_deadlines(fn):
"""
Decorator to make a function execute with `process_reveal_deadlines` OFF.
This is for testing long-range epochs transition without considering the reveal-deadline slashing effect.
"""
def entry(*args, spec: Spec, **kw):
if hasattr(spec, 'process_reveal_deadlines'):
old_state = spec.process_reveal_deadlines
spec.process_reveal_deadlines = lambda state: None
yield from fn(*args, spec=spec, **kw)
if hasattr(spec, 'process_reveal_deadlines'):
spec.process_reveal_deadlines = old_state
return with_meta_tags({'reveal_deadlines_setting': 1})(entry)
2019-06-06 21:30:40 +00:00
def with_all_phases(fn):
"""
2019-08-19 11:05:44 +00:00
A decorator for running a test with every phase
2019-06-06 21:30:40 +00:00
"""
return with_phases(ALL_PHASES)(fn)
2019-06-06 21:30:40 +00:00
def with_all_phases_except(exclusion_phases):
"""
A decorator factory for running a tests with every phase except the ones listed
"""
def decorator(fn):
return with_phases([phase for phase in ALL_PHASES if phase not in exclusion_phases])(fn)
2019-06-06 21:30:40 +00:00
return decorator
def with_phases(phases, other_phases=None):
2019-05-30 20:57:18 +00:00
"""
Decorator factory that returns a decorator that runs a test for the appropriate phases.
Additional phases that do not initially run, but are made available through the test, are optional.
2019-05-30 20:57:18 +00:00
"""
def decorator(fn):
def wrapper(*args, **kw):
run_phases = phases
# 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:
2021-03-26 23:08:50 +00:00
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:
2021-03-10 19:38:30 +00:00
available_phases |= set(other_phases)
2021-05-18 11:59:26 +00:00
preset_name = MINIMAL
if 'preset' in kw:
preset_name = kw.pop('preset')
targets = spec_targets[preset_name]
# TODO: test state is dependent on phase0 but is immediately transitioned to later phases.
# A new state-creation helper for later phases may be in place, and then tests can run without phase0
available_phases.add(PHASE0)
2021-03-09 00:16:29 +00:00
# Populate all phases for multi-phase tests
phase_dir = {}
2021-03-10 19:38:30 +00:00
if PHASE0 in available_phases:
2021-05-18 11:59:26 +00:00
phase_dir[PHASE0] = targets[PHASE0]
2021-03-11 13:22:38 +00:00
if ALTAIR in available_phases:
2021-05-18 11:59:26 +00:00
phase_dir[ALTAIR] = targets[ALTAIR]
if MERGE in available_phases:
2021-05-18 11:59:26 +00:00
phase_dir[MERGE] = targets[MERGE]
2021-03-26 23:08:50 +00:00
# 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:
2021-05-18 11:59:26 +00:00
ret = fn(spec=targets[PHASE0], phases=phase_dir, *args, **kw)
2021-03-11 13:22:38 +00:00
if ALTAIR in run_phases:
2021-05-18 11:59:26 +00:00
ret = fn(spec=targets[ALTAIR], phases=phase_dir, *args, **kw)
if MERGE in run_phases:
2021-05-18 11:59:26 +00:00
ret = fn(spec=targets[MERGE], phases=phase_dir, *args, **kw)
2021-03-27 00:42:23 +00:00
# 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.
2019-06-11 02:05:43 +00:00
return ret
return wrapper
return decorator
2021-05-07 03:30:45 +00:00
def with_presets(preset_bases, reason=None):
2021-05-18 11:59:26 +00:00
available_presets = set(preset_bases)
2021-05-07 03:30:45 +00:00
def decorator(fn):
def wrapper(*args, spec: Spec, **kw):
2021-05-18 11:59:26 +00:00
if spec.config.PRESET_BASE not in available_presets:
message = f"doesn't support this preset base: {spec.config.PRESET_BASE}."
if reason is not None:
message = f"{message} Reason: {reason}"
dump_skipping_message(message)
return None
return fn(*args, spec=spec, **kw)
return wrapper
return decorator
2021-05-07 03:30:45 +00:00
def with_config_overrides(config_overrides):
"""
Decorator that applies a dict of config value overrides to the spec during execution.
This may be slow due to having to reload the spec modules,
since the specs uses globals instead of a configuration object.
"""
def decorator(fn):
def wrapper(*args, spec: Spec, **kw):
# remember the old config
2021-05-18 11:59:26 +00:00
old_config = spec.config
2021-05-07 03:30:45 +00:00
# apply our overrides to a copy of it, and apply it to the spec
tmp_config = deepcopy(old_config)
tmp_config.update(config_overrides)
2021-05-18 11:59:26 +00:00
spec.config = tmp_config
2021-05-07 03:30:45 +00:00
# Run the function
out = fn(*args, spec=spec, **kw)
# If it's not returning None like a normal test function,
# it's generating things, and we need to complete it before setting back the config.
if out is not None:
yield from out
# Restore the old config and apply it
2021-05-18 11:59:26 +00:00
spec.config = old_config
2021-05-07 03:30:45 +00:00
return wrapper
return decorator
2021-03-11 13:22:38 +00:00
def is_post_altair(spec):
if spec.fork == MERGE: # TODO: remove parallel Altair-Merge condition after rebase.
return False
2021-04-13 15:21:40 +00:00
if spec.fork in FORKS_BEFORE_ALTAIR:
return False
return True
2021-05-04 22:18:01 +00:00
def is_post_merge(spec):
if spec.fork == ALTAIR: # TODO: remove parallel Altair-Merge condition after rebase.
return False
2021-05-04 22:18:01 +00:00
if spec.fork in FORKS_BEFORE_MERGE:
return False
return True
with_altair_and_later = with_phases([ALTAIR]) # TODO: include Merge, but not until Merge work is rebased.
with_merge_and_later = with_phases([MERGE])
2021-04-28 00:24:15 +00:00
def fork_transition_test(pre_fork_name, post_fork_name, fork_epoch=None):
"""
A decorator to construct a "transition" test from one fork of the eth2 spec
to another.
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
fork occurs or they must compute one (yielding to the generator) during the test
if more custom behavior is desired.
A test using this decorator should expect to receive as parameters:
`state`: the default state constructed for the `pre_fork_name` fork
according to the `with_state` decorator.
`fork_epoch`: the `fork_epoch` provided to this decorator, if given.
`spec`: the version of the eth2 spec corresponding to `pre_fork_name`.
`post_spec`: the version of the eth2 spec corresponding to `post_fork_name`.
`pre_tag`: a function to tag data as belonging to `pre_fork_name` fork.
Used to discriminate data during consumption of the generated spec tests.
`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