update ssz testing/debug utils

This commit is contained in:
protolambda 2019-06-19 02:37:22 +02:00
parent 5048b9e87a
commit a33c67894e
4 changed files with 84 additions and 111 deletions

View File

@ -1,39 +1,29 @@
from typing import Any
from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
is_uint_type, is_bool_type, is_list_type, SSZType, SSZValue, uint, Container, Bytes, List, Bit,
is_vector_type, is_bytes_type, is_bytesn_type, is_container_type,
read_vector_elem_type, read_list_elem_type,
Vector, BytesN Vector, BytesN
) )
def decode(data, typ): def decode(data: Any, typ: SSZType) -> SSZValue:
if is_uint_type(typ): if issubclass(typ, (uint, Bit)):
return data return typ(data)
elif is_bool_type(typ): elif issubclass(typ, (List, Vector)):
assert data in (True, False) return typ(decode(element, typ.elem_type) for element in data)
return data elif issubclass(typ, (Bytes, BytesN)):
elif is_list_type(typ): return typ(bytes.fromhex(data[2:]))
elem_typ = read_list_elem_type(typ) elif issubclass(typ, Container):
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):
temp = {} temp = {}
for field, subtype in typ.get_fields(): for field_name, field_type in typ.get_fields().items():
temp[field] = decode(data[field], subtype) temp[field_name] = decode(data[field_name], field_type)
if field + "_hash_tree_root" in data: if field_name + "_hash_tree_root" in data:
assert(data[field + "_hash_tree_root"][2:] == assert (data[field_name + "_hash_tree_root"][2:] ==
hash_tree_root(temp[field], subtype).hex()) hash_tree_root(temp[field_name]).hex())
ret = typ(**temp) ret = typ(**temp)
if "hash_tree_root" in data: if "hash_tree_root" in data:
assert(data["hash_tree_root"][2:] == assert (data["hash_tree_root"][2:] ==
hash_tree_root(ret, typ).hex()) hash_tree_root(ret).hex())
return ret return ret
else: else:
raise Exception(f"Type not recognized: data={data}, typ={typ}") raise Exception(f"Type not recognized: data={data}, typ={typ}")

View File

@ -1,36 +1,30 @@
from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
is_uint_type, is_bool_type, is_list_type, is_vector_type, is_container_type, SSZValue, uint, Container, Bytes, BytesN, List, Vector, Bit
read_elem_type,
uint
) )
def encode(value, typ, include_hash_tree_roots=False): def encode(value: SSZValue, include_hash_tree_roots=False):
if is_uint_type(typ): if isinstance(value, uint):
if hasattr(typ, '__supertype__'):
typ = typ.__supertype__
# Larger uints are boxed and the class declares their byte length # 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 str(value)
return value return value
elif is_bool_type(typ): elif isinstance(value, Bit):
assert value in (True, False) assert value in (True, False)
return value return value
elif is_list_type(typ) or is_vector_type(typ): elif isinstance(value, (List, Vector)):
elem_typ = read_elem_type(typ) return [encode(element, include_hash_tree_roots) for element in value]
return [encode(element, elem_typ, include_hash_tree_roots) for element in value] elif isinstance(value, (Bytes, BytesN)): # both bytes and BytesN
elif isinstance(typ, type) and issubclass(typ, bytes): # both bytes and BytesN
return '0x' + value.hex() return '0x' + value.hex()
elif is_container_type(typ): elif isinstance(value, Container):
ret = {} ret = {}
for field, subtype in typ.get_fields(): for field_value, field_name in zip(value, value.get_fields().keys()):
field_value = getattr(value, field) ret[field_name] = encode(field_value, include_hash_tree_roots)
ret[field] = encode(field_value, subtype, include_hash_tree_roots)
if 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: 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 return ret
else: else:
raise Exception(f"Type not recognized: value={value}, typ={typ}") raise Exception(f"Type not recognized: value={value}, typ={value.type()}")

View File

@ -1,18 +1,13 @@
from random import Random from random import Random
from typing import Any
from enum import Enum from enum import Enum
from eth2spec.utils.ssz.ssz_impl import is_basic_type
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
is_uint_type, is_bool_type, is_list_type, SSZType, SSZValue, BasicValue, BasicType, uint, Container, Bytes, List, Bit,
is_vector_type, is_bytes_type, is_bytesn_type, is_container_type, Vector, BytesN
read_vector_elem_type, read_list_elem_type,
uint_byte_size
) )
# in bytes # 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") random_mode_names = ("random", "zero", "max", "nil", "one", "lengthy")
@ -39,11 +34,11 @@ class RandomizationMode(Enum):
def get_random_ssz_object(rng: Random, def get_random_ssz_object(rng: Random,
typ: Any, typ: SSZType,
max_bytes_length: int, max_bytes_length: int,
max_list_length: int, max_list_length: int,
mode: RandomizationMode, mode: RandomizationMode,
chaos: bool) -> Any: chaos: bool) -> SSZValue:
""" """
Create an object for a given type, filled with random data. Create an object for a given type, filled with random data.
:param rng: The random number generator to use. :param rng: The random number generator to use.
@ -56,33 +51,31 @@ def get_random_ssz_object(rng: Random,
""" """
if chaos: if chaos:
mode = rng.choice(list(RandomizationMode)) mode = rng.choice(list(RandomizationMode))
if is_bytes_type(typ): if issubclass(typ, Bytes):
# Bytes array # Bytes array
if mode == RandomizationMode.mode_nil_count: if mode == RandomizationMode.mode_nil_count:
return b'' return typ(b'')
elif mode == RandomizationMode.mode_max_count: 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: 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: elif mode == RandomizationMode.mode_zero:
return b'\x00' return typ(b'\x00')
elif mode == RandomizationMode.mode_max: elif mode == RandomizationMode.mode_max:
return b'\xff' return typ(b'\xff')
else: else:
return get_random_bytes_list(rng, rng.randint(0, max_bytes_length)) return typ(get_random_bytes_list(rng, rng.randint(0, max_bytes_length)))
elif is_bytesn_type(typ): elif issubclass(typ, BytesN):
# BytesN
length = typ.length
# Sanity, don't generate absurdly big random values # Sanity, don't generate absurdly big random values
# If a client is aiming to performance-test, they should create a benchmark suite. # 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: if mode == RandomizationMode.mode_zero:
return b'\x00' * length return typ(b'\x00' * typ.length)
elif mode == RandomizationMode.mode_max: elif mode == RandomizationMode.mode_max:
return b'\xff' * length return typ(b'\xff' * typ.length)
else: else:
return get_random_bytes_list(rng, length) return typ(get_random_bytes_list(rng, typ.length))
elif is_basic_type(typ): elif issubclass(typ, BasicValue):
# Basic types # Basic types
if mode == RandomizationMode.mode_zero: if mode == RandomizationMode.mode_zero:
return get_min_basic_value(typ) return get_min_basic_value(typ)
@ -90,32 +83,28 @@ def get_random_ssz_object(rng: Random,
return get_max_basic_value(typ) return get_max_basic_value(typ)
else: else:
return get_random_basic_value(rng, typ) return get_random_basic_value(rng, typ)
elif is_vector_type(typ): elif issubclass(typ, Vector):
# Vector return typ(
elem_typ = read_vector_elem_type(typ) get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos)
return [
get_random_ssz_object(rng, elem_typ, max_bytes_length, max_list_length, mode, chaos)
for _ in range(typ.length) for _ in range(typ.length)
] )
elif is_list_type(typ): elif issubclass(typ, List):
# List length = rng.randint(0, min(typ.length, max_list_length))
elem_typ = read_list_elem_type(typ)
length = rng.randint(0, max_list_length)
if mode == RandomizationMode.mode_one_count: if mode == RandomizationMode.mode_one_count:
length = 1 length = 1
elif mode == RandomizationMode.mode_max_count: elif mode == RandomizationMode.mode_max_count:
length = max_list_length length = max_list_length
return [ return typ(
get_random_ssz_object(rng, elem_typ, max_bytes_length, max_list_length, mode, chaos) get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos)
for _ in range(length) for _ in range(length)
] )
elif is_container_type(typ): elif issubclass(typ, Container):
# Container # Container
return typ(**{ return typ(**{
field: field_name:
get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos)
for field, subtype in typ.get_fields() for field_name, field_type in typ.get_fields().items()
}) })
else: else:
raise Exception(f"Type not recognized: typ={typ}") 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)) return bytes(rng.getrandbits(8) for _ in range(length))
def get_random_basic_value(rng: Random, typ) -> Any: def get_random_basic_value(rng: Random, typ: BasicType) -> BasicValue:
if is_bool_type(typ): if issubclass(typ, Bit):
return rng.choice((True, False)) return typ(rng.choice((True, False)))
elif is_uint_type(typ): elif issubclass(typ, uint):
size = uint_byte_size(typ) assert typ.byte_len in UINT_BYTE_SIZES
assert size in UINT_SIZES return typ(rng.randint(0, 256 ** typ.byte_len - 1))
return rng.randint(0, 256**size - 1)
else: else:
raise ValueError(f"Not a basic type: typ={typ}") raise ValueError(f"Not a basic type: typ={typ}")
def get_min_basic_value(typ) -> Any: def get_min_basic_value(typ: BasicType) -> BasicValue:
if is_bool_type(typ): if issubclass(typ, Bit):
return False return typ(False)
elif is_uint_type(typ): elif issubclass(typ, uint):
size = uint_byte_size(typ) assert typ.byte_len in UINT_BYTE_SIZES
assert size in UINT_SIZES return typ(0)
return 0
else: else:
raise ValueError(f"Not a basic type: typ={typ}") raise ValueError(f"Not a basic type: typ={typ}")
def get_max_basic_value(typ) -> Any: def get_max_basic_value(typ: BasicType) -> BasicValue:
if is_bool_type(typ): if issubclass(typ, Bit):
return True return typ(True)
elif is_uint_type(typ): elif issubclass(typ, uint):
size = uint_byte_size(typ) assert typ.byte_len in UINT_BYTE_SIZES
assert size in UINT_SIZES return typ(256 ** typ.byte_len - 1)
return 256**size - 1
else: else:
raise ValueError(f"Not a basic type: typ={typ}") raise ValueError(f"Not a basic type: typ={typ}")

View File

@ -364,7 +364,10 @@ class BytesLike(Elements, metaclass=BytesType):
def __str__(self): def __str__(self):
cls = self.__class__ 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): class Bytes(BytesLike):