From a33c67894ebd9593fea94d38fd6914a3725ffe04 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 19 Jun 2019 02:37:22 +0200 Subject: [PATCH] update ssz testing/debug utils --- test_libs/pyspec/eth2spec/debug/decode.py | 44 +++---- test_libs/pyspec/eth2spec/debug/encode.py | 34 +++--- .../pyspec/eth2spec/debug/random_value.py | 112 ++++++++---------- .../pyspec/eth2spec/utils/ssz/ssz_typing.py | 5 +- 4 files changed, 84 insertions(+), 111 deletions(-) diff --git a/test_libs/pyspec/eth2spec/debug/decode.py b/test_libs/pyspec/eth2spec/debug/decode.py index 5ce116025..743479371 100644 --- a/test_libs/pyspec/eth2spec/debug/decode.py +++ b/test_libs/pyspec/eth2spec/debug/decode.py @@ -1,39 +1,29 @@ +from typing import Any from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ( - is_uint_type, is_bool_type, is_list_type, - is_vector_type, is_bytes_type, is_bytesn_type, is_container_type, - read_vector_elem_type, read_list_elem_type, + SSZType, SSZValue, uint, Container, Bytes, List, Bit, Vector, BytesN ) -def decode(data, typ): - if is_uint_type(typ): - return data - elif is_bool_type(typ): - assert data in (True, False) - return data - elif is_list_type(typ): - elem_typ = read_list_elem_type(typ) - return [decode(element, elem_typ) for element in data] - elif is_vector_type(typ): - elem_typ = read_vector_elem_type(typ) - return Vector(decode(element, elem_typ) for element in data) - elif is_bytes_type(typ): - return bytes.fromhex(data[2:]) - elif is_bytesn_type(typ): - return BytesN(bytes.fromhex(data[2:])) - elif is_container_type(typ): +def decode(data: Any, typ: SSZType) -> SSZValue: + if issubclass(typ, (uint, Bit)): + return typ(data) + elif issubclass(typ, (List, Vector)): + return typ(decode(element, typ.elem_type) for element in data) + elif issubclass(typ, (Bytes, BytesN)): + return typ(bytes.fromhex(data[2:])) + elif issubclass(typ, Container): temp = {} - for field, subtype in typ.get_fields(): - temp[field] = decode(data[field], subtype) - if field + "_hash_tree_root" in data: - assert(data[field + "_hash_tree_root"][2:] == - hash_tree_root(temp[field], subtype).hex()) + for field_name, field_type in typ.get_fields().items(): + temp[field_name] = decode(data[field_name], field_type) + if field_name + "_hash_tree_root" in data: + assert (data[field_name + "_hash_tree_root"][2:] == + hash_tree_root(temp[field_name]).hex()) ret = typ(**temp) if "hash_tree_root" in data: - assert(data["hash_tree_root"][2:] == - hash_tree_root(ret, typ).hex()) + assert (data["hash_tree_root"][2:] == + hash_tree_root(ret).hex()) return ret else: raise Exception(f"Type not recognized: data={data}, typ={typ}") diff --git a/test_libs/pyspec/eth2spec/debug/encode.py b/test_libs/pyspec/eth2spec/debug/encode.py index 61dd87928..d264bd7ff 100644 --- a/test_libs/pyspec/eth2spec/debug/encode.py +++ b/test_libs/pyspec/eth2spec/debug/encode.py @@ -1,36 +1,30 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ( - is_uint_type, is_bool_type, is_list_type, is_vector_type, is_container_type, - read_elem_type, - uint + SSZValue, uint, Container, Bytes, BytesN, List, Vector, Bit ) -def encode(value, typ, include_hash_tree_roots=False): - if is_uint_type(typ): - if hasattr(typ, '__supertype__'): - typ = typ.__supertype__ +def encode(value: SSZValue, include_hash_tree_roots=False): + if isinstance(value, uint): # Larger uints are boxed and the class declares their byte length - if issubclass(typ, uint) and typ.byte_len > 8: + if value.type().byte_len > 8: return str(value) return value - elif is_bool_type(typ): + elif isinstance(value, Bit): assert value in (True, False) return value - elif is_list_type(typ) or is_vector_type(typ): - elem_typ = read_elem_type(typ) - return [encode(element, elem_typ, include_hash_tree_roots) for element in value] - elif isinstance(typ, type) and issubclass(typ, bytes): # both bytes and BytesN + elif isinstance(value, (List, Vector)): + return [encode(element, include_hash_tree_roots) for element in value] + elif isinstance(value, (Bytes, BytesN)): # both bytes and BytesN return '0x' + value.hex() - elif is_container_type(typ): + elif isinstance(value, Container): ret = {} - for field, subtype in typ.get_fields(): - field_value = getattr(value, field) - ret[field] = encode(field_value, subtype, include_hash_tree_roots) + for field_value, field_name in zip(value, value.get_fields().keys()): + ret[field_name] = encode(field_value, include_hash_tree_roots) if include_hash_tree_roots: - ret[field + "_hash_tree_root"] = '0x' + hash_tree_root(field_value, subtype).hex() + ret[field_name + "_hash_tree_root"] = '0x' + hash_tree_root(field_value).hex() if include_hash_tree_roots: - ret["hash_tree_root"] = '0x' + hash_tree_root(value, typ).hex() + ret["hash_tree_root"] = '0x' + hash_tree_root(value).hex() return ret else: - raise Exception(f"Type not recognized: value={value}, typ={typ}") + raise Exception(f"Type not recognized: value={value}, typ={value.type()}") diff --git a/test_libs/pyspec/eth2spec/debug/random_value.py b/test_libs/pyspec/eth2spec/debug/random_value.py index 3edcc8808..bd40cb832 100644 --- a/test_libs/pyspec/eth2spec/debug/random_value.py +++ b/test_libs/pyspec/eth2spec/debug/random_value.py @@ -1,18 +1,13 @@ from random import Random -from typing import Any from enum import Enum -from eth2spec.utils.ssz.ssz_impl import is_basic_type - from eth2spec.utils.ssz.ssz_typing import ( - is_uint_type, is_bool_type, is_list_type, - is_vector_type, is_bytes_type, is_bytesn_type, is_container_type, - read_vector_elem_type, read_list_elem_type, - uint_byte_size + SSZType, SSZValue, BasicValue, BasicType, uint, Container, Bytes, List, Bit, + Vector, BytesN ) # in bytes -UINT_SIZES = (1, 2, 4, 8, 16, 32) +UINT_BYTE_SIZES = (1, 2, 4, 8, 16, 32) random_mode_names = ("random", "zero", "max", "nil", "one", "lengthy") @@ -39,11 +34,11 @@ class RandomizationMode(Enum): def get_random_ssz_object(rng: Random, - typ: Any, + typ: SSZType, max_bytes_length: int, max_list_length: int, mode: RandomizationMode, - chaos: bool) -> Any: + chaos: bool) -> SSZValue: """ Create an object for a given type, filled with random data. :param rng: The random number generator to use. @@ -56,33 +51,31 @@ def get_random_ssz_object(rng: Random, """ if chaos: mode = rng.choice(list(RandomizationMode)) - if is_bytes_type(typ): + if issubclass(typ, Bytes): # Bytes array if mode == RandomizationMode.mode_nil_count: - return b'' + return typ(b'') elif mode == RandomizationMode.mode_max_count: - return get_random_bytes_list(rng, max_bytes_length) + return typ(get_random_bytes_list(rng, max_bytes_length)) elif mode == RandomizationMode.mode_one_count: - return get_random_bytes_list(rng, 1) + return typ(get_random_bytes_list(rng, 1)) elif mode == RandomizationMode.mode_zero: - return b'\x00' + return typ(b'\x00') elif mode == RandomizationMode.mode_max: - return b'\xff' + return typ(b'\xff') else: - return get_random_bytes_list(rng, rng.randint(0, max_bytes_length)) - elif is_bytesn_type(typ): - # BytesN - length = typ.length + return typ(get_random_bytes_list(rng, rng.randint(0, max_bytes_length))) + elif issubclass(typ, BytesN): # Sanity, don't generate absurdly big random values # If a client is aiming to performance-test, they should create a benchmark suite. - assert length <= max_bytes_length + assert typ.length <= max_bytes_length if mode == RandomizationMode.mode_zero: - return b'\x00' * length + return typ(b'\x00' * typ.length) elif mode == RandomizationMode.mode_max: - return b'\xff' * length + return typ(b'\xff' * typ.length) else: - return get_random_bytes_list(rng, length) - elif is_basic_type(typ): + return typ(get_random_bytes_list(rng, typ.length)) + elif issubclass(typ, BasicValue): # Basic types if mode == RandomizationMode.mode_zero: return get_min_basic_value(typ) @@ -90,32 +83,28 @@ def get_random_ssz_object(rng: Random, return get_max_basic_value(typ) else: return get_random_basic_value(rng, typ) - elif is_vector_type(typ): - # Vector - elem_typ = read_vector_elem_type(typ) - return [ - get_random_ssz_object(rng, elem_typ, max_bytes_length, max_list_length, mode, chaos) + elif issubclass(typ, Vector): + return typ( + get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos) for _ in range(typ.length) - ] - elif is_list_type(typ): - # List - elem_typ = read_list_elem_type(typ) - length = rng.randint(0, max_list_length) + ) + elif issubclass(typ, List): + length = rng.randint(0, min(typ.length, max_list_length)) if mode == RandomizationMode.mode_one_count: length = 1 elif mode == RandomizationMode.mode_max_count: length = max_list_length - return [ - get_random_ssz_object(rng, elem_typ, max_bytes_length, max_list_length, mode, chaos) + return typ( + get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos) for _ in range(length) - ] - elif is_container_type(typ): + ) + elif issubclass(typ, Container): # Container return typ(**{ - field: - get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) - for field, subtype in typ.get_fields() + field_name: + get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos) + for field_name, field_type in typ.get_fields().items() }) else: raise Exception(f"Type not recognized: typ={typ}") @@ -125,34 +114,31 @@ def get_random_bytes_list(rng: Random, length: int) -> bytes: return bytes(rng.getrandbits(8) for _ in range(length)) -def get_random_basic_value(rng: Random, typ) -> Any: - if is_bool_type(typ): - return rng.choice((True, False)) - elif is_uint_type(typ): - size = uint_byte_size(typ) - assert size in UINT_SIZES - return rng.randint(0, 256**size - 1) +def get_random_basic_value(rng: Random, typ: BasicType) -> BasicValue: + if issubclass(typ, Bit): + return typ(rng.choice((True, False))) + elif issubclass(typ, uint): + assert typ.byte_len in UINT_BYTE_SIZES + return typ(rng.randint(0, 256 ** typ.byte_len - 1)) else: raise ValueError(f"Not a basic type: typ={typ}") -def get_min_basic_value(typ) -> Any: - if is_bool_type(typ): - return False - elif is_uint_type(typ): - size = uint_byte_size(typ) - assert size in UINT_SIZES - return 0 +def get_min_basic_value(typ: BasicType) -> BasicValue: + if issubclass(typ, Bit): + return typ(False) + elif issubclass(typ, uint): + assert typ.byte_len in UINT_BYTE_SIZES + return typ(0) else: raise ValueError(f"Not a basic type: typ={typ}") -def get_max_basic_value(typ) -> Any: - if is_bool_type(typ): - return True - elif is_uint_type(typ): - size = uint_byte_size(typ) - assert size in UINT_SIZES - return 256**size - 1 +def get_max_basic_value(typ: BasicType) -> BasicValue: + if issubclass(typ, Bit): + return typ(True) + elif issubclass(typ, uint): + assert typ.byte_len in UINT_BYTE_SIZES + return typ(256 ** typ.byte_len - 1) else: raise ValueError(f"Not a basic type: typ={typ}") diff --git a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py index 981f30d9b..ea4d85e82 100644 --- a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py +++ b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py @@ -364,7 +364,10 @@ class BytesLike(Elements, metaclass=BytesType): def __str__(self): cls = self.__class__ - return f"{cls.__name__}[{cls.length}]: {self.items.hex()}" + return f"{cls.__name__}[{cls.length}]: {self.hex()}" + + def hex(self) -> str: + return self.items.hex() class Bytes(BytesLike):