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,
)
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
@ -603,7 +603,7 @@ def objects_to_spec(preset_name: str,
out += f' # {vardef.comment}'
return out
config_spec = '@dataclass\nclass Configuration(object):\n'
config_spec = 'class Configuration(NamedTuple):\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"}'
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.
# It would be better if they would be more well-defined interfaces for stronger typing.
class Configuration(Protocol):
PRESET_BASE: str
class Spec(Protocol):
version: str
fork: str
config: Configuration
class SpecPhase0(Spec):
@ -78,8 +82,8 @@ def with_custom_state(balances_fn: Callable[[Any], Sequence[int]],
def deco(fn):
def entry(*args, spec: Spec, phases: SpecForks, **kw):
# make a key for the state
key = (spec.fork, spec.config.PRESET_BASE, spec.__file__, balances_fn, threshold_fn)
# 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)
global _custom_state_cache_dict
if key not in _custom_state_cache_dict:
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)))
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):
bad = False
try:
@ -380,9 +392,11 @@ def with_presets(preset_bases, reason=None):
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.
WARNING: the spec_test decorator must wrap this, to ensure the decorated test actually runs.
This decorator forces the test to yield, and pytest doesn't run generator tests, and instead silently passes it.
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 wrapper(*args, spec: Spec, **kw):
@ -390,9 +404,16 @@ def with_config_overrides(config_overrides):
old_config = spec.config
# 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)
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
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:
return Bytes32(sha256(x).digest())
try:
return Bytes32(sha256(x).digest())
except TypeError:
print(x)
raise Exception("bad")