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_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}")

View File

@ -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()}")

View File

@ -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}")

View File

@ -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):