diff --git a/test_libs/pyspec/eth2spec/fuzzing/__init__.py b/test_libs/pyspec/eth2spec/fuzzing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_libs/pyspec/eth2spec/fuzzing/decoder.py b/test_libs/pyspec/eth2spec/fuzzing/decoder.py new file mode 100644 index 000000000..a5d3dfd97 --- /dev/null +++ b/test_libs/pyspec/eth2spec/fuzzing/decoder.py @@ -0,0 +1,84 @@ +from eth2spec.utils.ssz import ssz_typing as spec_ssz +import ssz + + +def translate_typ(typ) -> ssz.BaseSedes: + """ + Translates a spec type to a Py-SSZ type description (sedes). + :param typ: The spec type, a class. + :return: The Py-SSZ equivalent. + """ + if spec_ssz.is_container_type(typ): + return ssz.Container( + [translate_typ(field_typ) for (field_name, field_typ) in typ.get_fields()]) + elif spec_ssz.is_bytesn_type(typ): + return ssz.ByteVector(typ.length) + elif spec_ssz.is_bytes_type(typ): + return ssz.ByteList() + elif spec_ssz.is_vector_type(typ): + return ssz.Vector(translate_typ(spec_ssz.read_vector_elem_type(typ)), typ.length) + elif spec_ssz.is_list_type(typ): + return ssz.List(translate_typ(spec_ssz.read_list_elem_type(typ))) + elif spec_ssz.is_bool_type(typ): + return ssz.boolean + elif spec_ssz.is_uint_type(typ): + size = spec_ssz.uint_byte_size(typ) + if size == 1: + return ssz.uint8 + elif size == 2: + return ssz.uint16 + elif size == 4: + return ssz.uint32 + elif size == 8: + return ssz.uint64 + elif size == 16: + return ssz.uint128 + elif size == 32: + return ssz.uint256 + else: + raise TypeError("invalid uint size") + else: + raise TypeError("Type not supported: {}".format(typ)) + + +def translate_value(value, typ): + """ + Translate a value output from Py-SSZ deserialization into the given spec type. + :param value: The PySSZ value + :param typ: The type from the spec to translate into + :return: the translated value + """ + if spec_ssz.is_uint_type(typ): + size = spec_ssz.uint_byte_size(typ) + if size == 1: + return spec_ssz.uint8(value) + elif size == 2: + return spec_ssz.uint16(value) + elif size == 4: + return spec_ssz.uint32(value) + elif size == 8: + # uint64 is default (TODO this is changing soon) + return value + elif size == 16: + return spec_ssz.uint128(value) + elif size == 32: + return spec_ssz.uint256(value) + else: + raise TypeError("invalid uint size") + elif spec_ssz.is_list_type(typ): + elem_typ = spec_ssz.read_elem_type(typ) + return [translate_value(elem, elem_typ) for elem in value] + elif spec_ssz.is_bool_type(typ): + return value + elif spec_ssz.is_vector_type(typ): + elem_typ = spec_ssz.read_elem_type(typ) + return typ(*(translate_value(elem, elem_typ) for elem in value)) + elif spec_ssz.is_bytesn_type(typ): + return typ(value) + elif spec_ssz.is_bytes_type(typ): + return value + elif spec_ssz.is_container_type(typ): + return typ(**{f_name: translate_value(f_val, f_typ) for (f_name, f_val, f_typ) + in zip(typ.get_field_names(), value, typ.get_field_types())}) + else: + raise TypeError("Type not supported: {}".format(typ)) diff --git a/test_libs/pyspec/eth2spec/fuzzing/test_decoder.py b/test_libs/pyspec/eth2spec/fuzzing/test_decoder.py new file mode 100644 index 000000000..26ee6e913 --- /dev/null +++ b/test_libs/pyspec/eth2spec/fuzzing/test_decoder.py @@ -0,0 +1,33 @@ +from eth2spec.fuzzing.decoder import translate_typ, translate_value +from eth2spec.phase0 import spec +from eth2spec.utils.ssz import ssz_impl as spec_ssz_impl +from random import Random +from eth2spec.debug import random_value + + +def test_decoder(): + rng = Random(123) + + # check these types only, Block covers a lot of operation types already. + for typ in [spec.BeaconBlock, spec.BeaconState, spec.IndexedAttestation, spec.AttestationDataAndCustodyBit]: + # create a random pyspec value + original = random_value.get_random_ssz_object(rng, typ, 100, 10, + mode=random_value.RandomizationMode.mode_random, + chaos=True) + # serialize it, using pyspec + pyspec_data = spec_ssz_impl.serialize(original) + # get the py-ssz type for it + block_sedes = translate_typ(typ) + # try decoding using the py-ssz type + raw_value = block_sedes.deserialize(pyspec_data) + + # serialize it using py-ssz + pyssz_data = block_sedes.serialize(raw_value) + # now check if the serialized form is equal. If so, we confirmed decoding and encoding to work. + assert pyspec_data == pyssz_data + + # now translate the py-ssz value in a pyspec-value + block = translate_value(raw_value, typ) + + # and see if the hash-tree-root of the original matches the hash-tree-root of the decoded & translated value. + assert spec_ssz_impl.hash_tree_root(original) == spec_ssz_impl.hash_tree_root(block) diff --git a/test_libs/pyspec/requirements.txt b/test_libs/pyspec/requirements.txt index 3b38930bd..2de2aa84b 100644 --- a/test_libs/pyspec/requirements.txt +++ b/test_libs/pyspec/requirements.txt @@ -3,3 +3,4 @@ eth-typing>=2.1.0,<3.0.0 pycryptodome==3.7.3 py_ecc>=1.6.0 typing_inspect==0.4.0 +ssz==0.1.0a10 diff --git a/test_libs/pyspec/setup.py b/test_libs/pyspec/setup.py index e99b911ee..3856640ab 100644 --- a/test_libs/pyspec/setup.py +++ b/test_libs/pyspec/setup.py @@ -9,6 +9,7 @@ setup( "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.7.3", "py_ecc>=1.6.0", - "typing_inspect==0.4.0" + "typing_inspect==0.4.0", + "ssz==0.1.0a10" ] )