diff --git a/test_generators/ssz_generic/main.py b/test_generators/ssz_generic/main.py index 5f223eb29..83e6da86d 100644 --- a/test_generators/ssz_generic/main.py +++ b/test_generators/ssz_generic/main.py @@ -5,6 +5,7 @@ import ssz_bitlist import ssz_bitvector import ssz_boolean import ssz_uints +import ssz_container def create_provider(handler_name: str, suite_name: str, case_maker) -> gen_typing.TestProvider: @@ -38,4 +39,6 @@ if __name__ == "__main__": create_provider("boolean", "invalid", ssz_boolean.invalid_cases), create_provider("uints", "valid", ssz_uints.valid_cases), create_provider("uints", "invalid", ssz_uints.invalid_cases), + create_provider("containers", "valid", ssz_container.valid_cases), + create_provider("containers", "invalid", ssz_container.invalid_cases), ]) diff --git a/test_generators/ssz_generic/ssz_basic_vector.py b/test_generators/ssz_generic/ssz_basic_vector.py index fa51113d9..6e7e08daa 100644 --- a/test_generators/ssz_generic/ssz_basic_vector.py +++ b/test_generators/ssz_generic/ssz_basic_vector.py @@ -1,4 +1,4 @@ -from .ssz_test_case import invalid_test_case, valid_test_case +from ssz_test_case import invalid_test_case, valid_test_case from eth2spec.utils.ssz.ssz_typing import boolean, uint8, uint16, uint32, uint64, uint128, uint256, Vector, BasicType from eth2spec.utils.ssz.ssz_impl import serialize from random import Random @@ -27,23 +27,34 @@ BASIC_TYPES: Dict[str, BasicType] = { def valid_cases(): rng = Random(1234) for (name, typ) in BASIC_TYPES.items(): + random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max] + if name != 'bool': + random_modes.append(RandomizationMode.mode_random) for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: - for mode in [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max]: - yield f'vec_{name}_{length}_{mode.to_name()}',\ + for mode in random_modes: + yield f'vec_{name}_{length}_{mode.to_name()}', \ valid_test_case(lambda: basic_vector_case_fn(rng, mode, typ, length)) def invalid_cases(): # zero length vectors are illegal - for (name, typ) in BASIC_TYPES: - yield f'vec_{name}_0', lambda: b'' + for (name, typ) in BASIC_TYPES.items(): + yield f'vec_{name}_0', invalid_test_case(lambda: b'') rng = Random(1234) for (name, typ) in BASIC_TYPES.items(): + random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max] + if name != 'bool': + random_modes.append(RandomizationMode.mode_random) for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: yield f'vec_{name}_{length}_nil', invalid_test_case(lambda: b'') - for mode in [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max]: + for mode in random_modes: yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \ invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length - 1))) yield f'vec_{name}_{length}_{mode.to_name()}_one_more', \ invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length + 1))) + yield f'vec_{name}_{length}_{mode.to_name()}_one_byte_less', \ + invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length))[:-1]) + yield f'vec_{name}_{length}_{mode.to_name()}_one_byte_more', \ + invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length)) + + serialize(basic_vector_case_fn(rng, mode, uint8, 1))) diff --git a/test_generators/ssz_generic/ssz_bitlist.py b/test_generators/ssz_generic/ssz_bitlist.py index e0c756aeb..d1a940eee 100644 --- a/test_generators/ssz_generic/ssz_bitlist.py +++ b/test_generators/ssz_generic/ssz_bitlist.py @@ -1,4 +1,4 @@ -from .ssz_test_case import invalid_test_case, valid_test_case +from ssz_test_case import invalid_test_case, valid_test_case from eth2spec.utils.ssz.ssz_typing import Bitlist from eth2spec.utils.ssz.ssz_impl import serialize from random import Random diff --git a/test_generators/ssz_generic/ssz_bitvector.py b/test_generators/ssz_generic/ssz_bitvector.py index ab3b6831d..2b04577e8 100644 --- a/test_generators/ssz_generic/ssz_bitvector.py +++ b/test_generators/ssz_generic/ssz_bitvector.py @@ -1,4 +1,4 @@ -from .ssz_test_case import invalid_test_case, valid_test_case +from ssz_test_case import invalid_test_case, valid_test_case from eth2spec.utils.ssz.ssz_typing import Bitvector from eth2spec.utils.ssz.ssz_impl import serialize from random import Random @@ -21,7 +21,7 @@ def valid_cases(): def invalid_cases(): # zero length bitvecors are illegal - yield 'bitvec_0', lambda: b'' + yield 'bitvec_0', invalid_test_case(lambda: b'') rng = Random(1234) for (typ_size, test_size) in [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (8, 9), (9, 8), (16, 8), (32, 33), (512, 513)]: diff --git a/test_generators/ssz_generic/ssz_boolean.py b/test_generators/ssz_generic/ssz_boolean.py index 4463ab3e2..9ff36ba88 100644 --- a/test_generators/ssz_generic/ssz_boolean.py +++ b/test_generators/ssz_generic/ssz_boolean.py @@ -1,4 +1,4 @@ -from .ssz_test_case import valid_test_case, invalid_test_case +from ssz_test_case import valid_test_case, invalid_test_case from eth2spec.utils.ssz.ssz_typing import boolean diff --git a/test_generators/ssz_generic/ssz_container.py b/test_generators/ssz_generic/ssz_container.py new file mode 100644 index 000000000..7dbd5e111 --- /dev/null +++ b/test_generators/ssz_generic/ssz_container.py @@ -0,0 +1,120 @@ +from ssz_test_case import invalid_test_case, valid_test_case +from eth2spec.utils.ssz.ssz_typing import SSZType, Container, byte, uint8, uint16, \ + uint32, uint64, List, Bytes, Vector, Bitvector, Bitlist +from eth2spec.utils.ssz.ssz_impl import serialize +from random import Random +from typing import Dict, Tuple, Sequence, Callable +from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object + + +class SingleFieldTestStruct(Container): + A: byte + + +class SmallTestStruct(Container): + A: uint16 + B: uint16 + + +class FixedTestStruct(Container): + A: uint8 + B: uint64 + C: uint32 + + +class VarTestStruct(Container): + A: uint16 + B: List[uint16, 1024] + C: uint8 + + +class ComplexTestStruct(Container): + A: uint16 + B: List[uint16, 128] + C: uint8 + D: Bytes[256] + E: VarTestStruct + F: Vector[FixedTestStruct, 4] + G: Vector[VarTestStruct, 2] + + +class BitsStruct(Container): + A: Bitlist[5] + B: Bitvector[2] + C: Bitvector[1] + D: Bitlist[6] + E: Bitvector[8] + + +def container_case_fn(rng: Random, mode: RandomizationMode, typ: SSZType): + return get_random_ssz_object(rng, typ, + max_bytes_length=2000, + max_list_length=2000, + mode=mode, chaos=False) + + +PRESET_CONTAINERS: Dict[str, Tuple[SSZType, Sequence[int]]] = { + 'SingleFieldTestStruct': (SingleFieldTestStruct, []), + 'SmallTestStruct': (SmallTestStruct, []), + 'FixedTestStruct': (FixedTestStruct, []), + 'VarTestStruct': (VarTestStruct, [2]), + 'ComplexTestStruct': (ComplexTestStruct, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]), + 'BitsStruct': (BitsStruct, [0, 4 + 1 + 1, 4 + 1 + 1 + 4]), +} + + +def valid_cases(): + rng = Random(1234) + for (name, (typ, offsets)) in PRESET_CONTAINERS.items(): + for mode in [RandomizationMode.mode_zero, RandomizationMode.mode_max]: + yield f'{name}_{mode.to_name()}', valid_test_case(lambda: container_case_fn(rng, mode, typ)) + random_modes = [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max] + if len(offsets) != 0: + random_modes.extend([RandomizationMode.mode_nil_count, + RandomizationMode.mode_one_count, + RandomizationMode.mode_max_count]) + for mode in random_modes: + for variation in range(10): + yield f'{name}_{mode.to_name()}_{variation}', \ + valid_test_case(lambda: container_case_fn(rng, mode, typ)) + for variation in range(3): + yield f'{name}_{mode.to_name()}_chaos_{variation}', \ + valid_test_case(lambda: container_case_fn(rng, mode, typ)) + + +def mod_offset(b: bytes, offset_index: int, change: Callable[[int], int]): + return b[:offset_index] + \ + (change(int.from_bytes(b[offset_index:offset_index + 4], byteorder='little')) & 0xffffffff) \ + .to_bytes(length=4, byteorder='little') + \ + b[offset_index + 4:] + + +def invalid_cases(): + rng = Random(1234) + for (name, (typ, offsets)) in PRESET_CONTAINERS.items(): + # using mode_max_count, so that the extra byte cannot be picked up as normal list content + yield f'{name}_extra_byte', \ + invalid_test_case(lambda: serialize( + container_case_fn(rng, RandomizationMode.mode_max_count, typ)) + b'\xff') + + if len(offsets) != 0: + # Note: there are many more ways to have invalid offsets, + # these are just example to get clients started looking into hardening ssz. + for mode in [RandomizationMode.mode_random, + RandomizationMode.mode_nil_count, + RandomizationMode.mode_one_count, + RandomizationMode.mode_max_count]: + if len(offsets) != 0: + for offset_index in offsets: + yield f'{name}_offset_{offset_index}_plus_one', \ + invalid_test_case(lambda: mod_offset( + b=serialize(container_case_fn(rng, mode, typ)), + offset_index=offset_index, + change=lambda x: x + 1 + )) + yield f'{name}_offset_{offset_index}_zeroed', \ + invalid_test_case(lambda: mod_offset( + b=serialize(container_case_fn(rng, mode, typ)), + offset_index=offset_index, + change=lambda x: 0 + )) diff --git a/test_generators/ssz_generic/ssz_uints.py b/test_generators/ssz_generic/ssz_uints.py index 93af6b91e..b21fb251c 100644 --- a/test_generators/ssz_generic/ssz_uints.py +++ b/test_generators/ssz_generic/ssz_uints.py @@ -1,4 +1,4 @@ -from .ssz_test_case import invalid_test_case, valid_test_case +from ssz_test_case import invalid_test_case, valid_test_case from eth2spec.utils.ssz.ssz_typing import BasicType, uint8, uint16, uint32, uint64, uint128, uint256 from random import Random from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object