from random import Random from enum import Enum from eth2spec.utils.ssz.ssz_typing import ( SSZType, SSZValue, BasicValue, BasicType, uint, Container, ByteList, List, boolean, Vector, ByteVector, Bitlist, Bitvector ) # in bytes UINT_BYTE_SIZES = (1, 2, 4, 8, 16, 32) random_mode_names = ("random", "zero", "max", "nil", "one", "lengthy") class RandomizationMode(Enum): # random content / length mode_random = 0 # Zero-value mode_zero = 1 # Maximum value, limited to count 1 however mode_max = 2 # Return 0 values, i.e. empty mode_nil_count = 3 # Return 1 value, random content mode_one_count = 4 # Return max amount of values, random content mode_max_count = 5 def to_name(self): return random_mode_names[self.value] def is_changing(self): return self.value in [0, 4, 5] def get_random_ssz_object(rng: Random, typ: SSZType, max_bytes_length: int, max_list_length: int, mode: RandomizationMode, chaos: bool) -> SSZValue: """ Create an object for a given type, filled with random data. :param rng: The random number generator to use. :param typ: The type to instantiate :param max_bytes_length: the max. length for a random bytes array :param max_list_length: the max. length for a random list :param mode: how to randomize :param chaos: if true, the randomization-mode will be randomly changed :return: the random object instance, of the given type. """ if chaos: mode = rng.choice(list(RandomizationMode)) if issubclass(typ, ByteList): # ByteList array if mode == RandomizationMode.mode_nil_count: return typ(b'') elif mode == RandomizationMode.mode_max_count: return typ(get_random_bytes_list(rng, min(max_bytes_length, typ.length))) elif mode == RandomizationMode.mode_one_count: return typ(get_random_bytes_list(rng, min(1, typ.length))) elif mode == RandomizationMode.mode_zero: return typ(b'\x00' * min(1, typ.length)) elif mode == RandomizationMode.mode_max: return typ(b'\xff' * min(1, typ.length)) else: return typ(get_random_bytes_list(rng, rng.randint(0, min(max_bytes_length, typ.length)))) elif issubclass(typ, ByteVector): # Sanity, don't generate absurdly big random values # If a client is aiming to performance-test, they should create a benchmark suite. assert typ.length <= max_bytes_length if mode == RandomizationMode.mode_zero: return typ(b'\x00' * typ.length) elif mode == RandomizationMode.mode_max: return typ(b'\xff' * typ.length) else: 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) elif mode == RandomizationMode.mode_max: return get_max_basic_value(typ) else: return get_random_basic_value(rng, typ) elif issubclass(typ, Vector) or issubclass(typ, Bitvector): return typ( get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos) for _ in range(typ.length) ) elif issubclass(typ, List) or issubclass(typ, Bitlist): 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 elif mode == RandomizationMode.mode_nil_count: length = 0 if typ.length < length: # SSZ imposes a hard limit on lists, we can't put in more than that length = typ.length return typ( get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos) for _ in range(length) ) elif issubclass(typ, Container): # Container return typ(**{ 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}") 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: BasicType) -> BasicValue: if issubclass(typ, boolean): 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: BasicType) -> BasicValue: if issubclass(typ, boolean): 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: BasicType) -> BasicValue: if issubclass(typ, boolean): 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}")