switch configuration to named tuple for reliable hashing, add test for config override functionality

This commit is contained in:
protolambda 2021-05-18 16:08:30 +02:00
parent e8b0c46138
commit f5c647b47b
No known key found for this signature in database
GPG Key ID: EC89FDBB2B4C7623
4 changed files with 55 additions and 11 deletions

View File

@ -326,7 +326,7 @@ from dataclasses import (
field, field,
) )
from typing import ( from typing import (
Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple
) )
from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes
@ -603,7 +603,7 @@ def objects_to_spec(preset_name: str,
out += f' # {vardef.comment}' out += f' # {vardef.comment}'
return out return out
config_spec = '@dataclass\nclass Configuration(object):\n' config_spec = 'class Configuration(NamedTuple):\n'
config_spec += ' PRESET_BASE: str\n' config_spec += ' PRESET_BASE: str\n'
config_spec += '\n'.join(f' {k}: {v.type_name if v.type_name is not None else "int"}' config_spec += '\n'.join(f' {k}: {v.type_name if v.type_name is not None else "int"}'
for k, v in spec_object.config_vars.items()) for k, v in spec_object.config_vars.items())

View File

@ -0,0 +1,19 @@
from eth2spec.test.context import spec_configured_state_test, with_phases
from eth2spec.test.helpers.constants import ALTAIR
@with_phases([ALTAIR])
@spec_configured_state_test({
'GENESIS_FORK_VERSION': '0x12345678',
'ALTAIR_FORK_VERSION': '0x11111111',
'ALTAIR_FORK_EPOCH': 4
})
def test_config_override(spec, state):
assert spec.config.ALTAIR_FORK_EPOCH == 4
assert spec.config.GENESIS_FORK_VERSION != spec.Version('0x00000000')
assert spec.config.GENESIS_FORK_VERSION == spec.Version('0x12345678')
assert spec.config.ALTAIR_FORK_VERSION == spec.Version('0x11111111')
assert state.fork.current_version == spec.Version('0x11111111')
# TODO: it would be nice if the create_genesis_state actually outputs a state
# for the fork with a slot that matches at least the fork boundary.
# assert spec.get_current_epoch(state) >= 4

View File

@ -23,9 +23,13 @@ from lru import LRU
# TODO: currently phases are defined as python modules. # TODO: currently phases are defined as python modules.
# It would be better if they would be more well-defined interfaces for stronger typing. # It would be better if they would be more well-defined interfaces for stronger typing.
class Configuration(Protocol):
PRESET_BASE: str
class Spec(Protocol): class Spec(Protocol):
version: str fork: str
config: Configuration
class SpecPhase0(Spec): class SpecPhase0(Spec):
@ -78,8 +82,8 @@ def with_custom_state(balances_fn: Callable[[Any], Sequence[int]],
def deco(fn): def deco(fn):
def entry(*args, spec: Spec, phases: SpecForks, **kw): def entry(*args, spec: Spec, phases: SpecForks, **kw):
# make a key for the state # make a key for the state, unique to the fork + config (incl preset choice) and balances/activations
key = (spec.fork, spec.config.PRESET_BASE, spec.__file__, balances_fn, threshold_fn) key = (spec.fork, spec.config.__hash__(), spec.__file__, balances_fn, threshold_fn)
global _custom_state_cache_dict global _custom_state_cache_dict
if key not in _custom_state_cache_dict: if key not in _custom_state_cache_dict:
state = _prepare_state(balances_fn, threshold_fn, spec, phases) state = _prepare_state(balances_fn, threshold_fn, spec, phases)
@ -211,6 +215,14 @@ def spec_state_test(fn):
return spec_test(with_state(single_phase(fn))) return spec_test(with_state(single_phase(fn)))
def spec_configured_state_test(conf):
overrides = with_config_overrides(conf)
def decorator(fn):
return spec_test(overrides(with_state(single_phase(fn))))
return decorator
def expect_assertion_error(fn): def expect_assertion_error(fn):
bad = False bad = False
try: try:
@ -380,9 +392,11 @@ def with_presets(preset_bases, reason=None):
def with_config_overrides(config_overrides): def with_config_overrides(config_overrides):
""" """
Decorator that applies a dict of config value overrides to the spec during execution. WARNING: the spec_test decorator must wrap this, to ensure the decorated test actually runs.
This may be slow due to having to reload the spec modules, This decorator forces the test to yield, and pytest doesn't run generator tests, and instead silently passes it.
since the specs uses globals instead of a configuration object. Use 'spec_configured_state_test' instead of 'spec_state_test' if you are unsure.
This is a decorator that applies a dict of config value overrides to the spec during execution.
""" """
def decorator(fn): def decorator(fn):
def wrapper(*args, spec: Spec, **kw): def wrapper(*args, spec: Spec, **kw):
@ -390,9 +404,16 @@ def with_config_overrides(config_overrides):
old_config = spec.config old_config = spec.config
# apply our overrides to a copy of it, and apply it to the spec # apply our overrides to a copy of it, and apply it to the spec
tmp_config = deepcopy(old_config) tmp_config = deepcopy(old_config._asdict()) # not a private method, there are multiple
tmp_config.update(config_overrides) tmp_config.update(config_overrides)
spec.config = tmp_config config_types = spec.Configuration.__annotations__
# Retain types of all config values
test_config = {k: config_types[k](v) for k, v in tmp_config.items()}
# Output the config for test vectors (TODO: check config YAML encoding)
yield 'config', test_config
spec.config = spec.Configuration(**test_config)
# Run the function # Run the function
out = fn(*args, spec=spec, **kw) out = fn(*args, spec=spec, **kw)

View File

@ -6,4 +6,8 @@ ZERO_BYTES32 = b'\x00' * 32
def hash(x: Union[bytes, bytearray, memoryview]) -> Bytes32: def hash(x: Union[bytes, bytearray, memoryview]) -> Bytes32:
try:
return Bytes32(sha256(x).digest()) return Bytes32(sha256(x).digest())
except TypeError:
print(x)
raise Exception("bad")