Skip to content

Commit a2236be

Browse files
committed
Merge branch 'eip-7495' into ef-eip7688
2 parents 460d46d + d281ded commit a2236be

7 files changed

Lines changed: 456 additions & 3 deletions

File tree

tests/core/pyspec/eth2spec/debug/decode.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
33
from eth2spec.utils.ssz.ssz_typing import (
44
uint, Container, List, boolean,
5-
Vector, ByteVector, ByteList, Union, View
5+
Vector, ByteVector, ByteList, Union, View,
6+
Profile, StableContainer,
67
)
78

89

@@ -27,6 +28,43 @@ def decode(data: Any, typ):
2728
assert (data["hash_tree_root"][2:] ==
2829
hash_tree_root(ret).hex())
2930
return ret
31+
elif issubclass(typ, StableContainer):
32+
temp = {}
33+
for field_name, field_type in typ.fields().items():
34+
if data[field_name] is None:
35+
temp[field_name] = None
36+
if field_name + "_hash_tree_root" in data:
37+
assert (data[field_name + "_hash_tree_root"][2:] ==
38+
'00' * 32)
39+
else:
40+
temp[field_name] = decode(data[field_name], field_type)
41+
if field_name + "_hash_tree_root" in data:
42+
assert (data[field_name + "_hash_tree_root"][2:] ==
43+
hash_tree_root(temp[field_name]).hex())
44+
ret = typ(**temp)
45+
if "hash_tree_root" in data:
46+
assert (data["hash_tree_root"][2:] ==
47+
hash_tree_root(ret).hex())
48+
return ret
49+
elif issubclass(typ, Profile):
50+
temp = {}
51+
for field_name, [field_type, is_optional] in typ.fields().items():
52+
if data[field_name] is None:
53+
assert is_optional
54+
temp[field_name] = None
55+
if field_name + "_hash_tree_root" in data:
56+
assert (data[field_name + "_hash_tree_root"][2:] ==
57+
'00' * 32)
58+
else:
59+
temp[field_name] = decode(data[field_name], field_type)
60+
if field_name + "_hash_tree_root" in data:
61+
assert (data[field_name + "_hash_tree_root"][2:] ==
62+
hash_tree_root(temp[field_name]).hex())
63+
ret = typ(**temp)
64+
if "hash_tree_root" in data:
65+
assert (data["hash_tree_root"][2:] ==
66+
hash_tree_root(ret).hex())
67+
return ret
3068
elif issubclass(typ, Union):
3169
selector = int(data["selector"])
3270
options = typ.options()

tests/core/pyspec/eth2spec/debug/encode.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from eth2spec.utils.ssz.ssz_impl import hash_tree_root, serialize
22
from eth2spec.utils.ssz.ssz_typing import (
33
uint, boolean,
4-
Bitlist, Bitvector, Container, Vector, List, Union
4+
Bitlist, Bitvector, Container, Vector, List, Union,
5+
Profile, StableContainer,
56
)
67

78

@@ -31,6 +32,21 @@ def encode(value, include_hash_tree_roots=False):
3132
if include_hash_tree_roots:
3233
ret["hash_tree_root"] = '0x' + hash_tree_root(value).hex()
3334
return ret
35+
elif isinstance(value, (StableContainer, Profile)):
36+
ret = {}
37+
for field_name in value.fields().keys():
38+
field_value = getattr(value, field_name)
39+
if field_value is None:
40+
ret[field_name] = None
41+
if include_hash_tree_roots:
42+
ret[field_name + "_hash_tree_root"] = '0x' + '00' * 32
43+
else:
44+
ret[field_name] = encode(field_value, include_hash_tree_roots)
45+
if include_hash_tree_roots:
46+
ret[field_name + "_hash_tree_root"] = '0x' + hash_tree_root(field_value).hex()
47+
if include_hash_tree_roots:
48+
ret["hash_tree_root"] = '0x' + hash_tree_root(value).hex()
49+
return ret
3450
elif isinstance(value, Union):
3551
inner_value = value.value()
3652
return {

tests/core/pyspec/eth2spec/debug/random_value.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
from eth2spec.utils.ssz.ssz_typing import (
77
View, BasicView, uint, Container, List, boolean,
8-
Vector, ByteVector, ByteList, Bitlist, Bitvector, Union
8+
Vector, ByteVector, ByteList, Bitlist, Bitvector, Union,
9+
Profile, StableContainer,
910
)
1011

1112
# in bytes
@@ -115,6 +116,29 @@ def get_random_ssz_object(rng: Random,
115116
get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos)
116117
for field_name, field_type in fields.items()
117118
})
119+
elif issubclass(typ, StableContainer):
120+
fields = typ.fields()
121+
# StableContainer
122+
return typ(**{
123+
field_name:
124+
rng.choice([
125+
None,
126+
get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos)
127+
])
128+
for field_name, field_type in fields.items()
129+
})
130+
elif issubclass(typ, Profile):
131+
fields = typ.fields()
132+
# Profile
133+
return typ(**{
134+
field_name:
135+
rng.choice([
136+
None if is_optional else get_random_ssz_object(
137+
rng, field_type, max_bytes_length, max_list_length, mode, chaos),
138+
get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos)
139+
])
140+
for field_name, [field_type, is_optional] in fields.items()
141+
})
118142
elif issubclass(typ, Union):
119143
options = typ.options()
120144
selector: int

tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from remerkleable.bitfields import Bitvector, Bitlist
88
from remerkleable.byte_arrays import ByteVector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, ByteList
99
from remerkleable.core import BasicView, View, Path
10+
from remerkleable.stable_container import Profile, StableContainer
1011

1112

1213
Bytes20 = ByteVector[20] # type: ignore

tests/generators/ssz_generic/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import ssz_boolean
77
import ssz_uints
88
import ssz_container
9+
import ssz_stablecontainer
10+
import ssz_profile
911
from eth2spec.test.helpers.constants import PHASE0
1012

1113

@@ -43,4 +45,6 @@ def cases_fn() -> Iterable[gen_typing.TestCase]:
4345
create_provider("uints", "invalid", ssz_uints.invalid_cases),
4446
create_provider("containers", "valid", ssz_container.valid_cases),
4547
create_provider("containers", "invalid", ssz_container.invalid_cases),
48+
create_provider("stablecontainers", "valid", ssz_stablecontainer.valid_cases),
49+
create_provider("profiles", "valid", ssz_profile.valid_cases),
4650
])
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
from ssz_test_case import invalid_test_case, valid_test_case
2+
from eth2spec.utils.ssz.ssz_typing import View, byte, uint8, uint16, \
3+
uint32, uint64, List, ByteList, Vector, Bitvector, Bitlist, Profile
4+
from eth2spec.utils.ssz.ssz_impl import serialize
5+
from random import Random
6+
from typing import Dict, Tuple, Sequence, Callable, Type
7+
from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object
8+
from ssz_stablecontainer import SingleFieldTestStableStruct, SmallTestStableStruct, FixedTestStableStruct, \
9+
VarTestStableStruct, ComplexTestStableStruct, BitsStableStruct
10+
11+
12+
class SingleFieldTestProfile(Profile[SingleFieldTestStableStruct]):
13+
A: byte
14+
15+
16+
class SmallTestProfile1(Profile[SmallTestStableStruct]):
17+
A: uint16
18+
B: uint16
19+
20+
21+
class SmallTestProfile2(Profile[SmallTestStableStruct]):
22+
A: uint16
23+
24+
25+
class SmallTestProfile3(Profile[SmallTestStableStruct]):
26+
B: uint16
27+
28+
29+
class FixedTestProfile1(Profile[FixedTestStableStruct]):
30+
A: uint8
31+
B: uint64
32+
C: uint32
33+
34+
35+
class FixedTestProfile2(Profile[FixedTestStableStruct]):
36+
A: uint8
37+
B: uint64
38+
39+
40+
class FixedTestProfile3(Profile[FixedTestStableStruct]):
41+
A: uint8
42+
C: uint32
43+
44+
45+
class FixedTestProfile4(Profile[FixedTestStableStruct]):
46+
C: uint32
47+
48+
49+
class VarTestProfile1(Profile[VarTestStableStruct]):
50+
A: uint16
51+
B: List[uint16, 1024]
52+
C: uint8
53+
54+
55+
class VarTestProfile2(Profile[VarTestStableStruct]):
56+
B: List[uint16, 1024]
57+
C: uint8
58+
59+
60+
class VarTestProfile3(Profile[VarTestStableStruct]):
61+
B: List[uint16, 1024]
62+
63+
64+
class ComplexTestProfile1(Profile[ComplexTestStableStruct]):
65+
A: uint16
66+
B: List[uint16, 128]
67+
C: uint8
68+
D: ByteList[256]
69+
E: VarTestStableStruct
70+
F: Vector[FixedTestStableStruct, 4]
71+
G: Vector[VarTestStableStruct, 2]
72+
73+
74+
class ComplexTestProfile2(Profile[ComplexTestStableStruct]):
75+
A: uint16
76+
B: List[uint16, 128]
77+
C: uint8
78+
D: ByteList[256]
79+
E: VarTestStableStruct
80+
81+
82+
class ComplexTestProfile3(Profile[ComplexTestStableStruct]):
83+
A: uint16
84+
C: uint8
85+
E: VarTestStableStruct
86+
G: Vector[VarTestStableStruct, 2]
87+
88+
89+
class ComplexTestProfile4(Profile[ComplexTestStableStruct]):
90+
B: List[uint16, 128]
91+
D: ByteList[256]
92+
F: Vector[FixedTestStableStruct, 4]
93+
94+
95+
class ComplexTestProfile5(Profile[ComplexTestStableStruct]):
96+
E: VarTestStableStruct
97+
F: Vector[FixedTestStableStruct, 4]
98+
G: Vector[VarTestStableStruct, 2]
99+
100+
101+
class BitsProfile1(Profile[BitsStableStruct]):
102+
A: Bitlist[5]
103+
B: Bitvector[2]
104+
C: Bitvector[1]
105+
D: Bitlist[6]
106+
E: Bitvector[8]
107+
108+
109+
class BitsProfile2(Profile[BitsStableStruct]):
110+
A: Bitlist[5]
111+
B: Bitvector[2]
112+
C: Bitvector[1]
113+
D: Bitlist[6]
114+
115+
116+
class BitsProfile3(Profile[BitsStableStruct]):
117+
A: Bitlist[5]
118+
D: Bitlist[6]
119+
E: Bitvector[8]
120+
121+
122+
def container_case_fn(rng: Random, mode: RandomizationMode, typ: Type[View], chaos: bool=False):
123+
return get_random_ssz_object(rng, typ,
124+
max_bytes_length=2000,
125+
max_list_length=2000,
126+
mode=mode, chaos=chaos)
127+
128+
129+
PRESET_CONTAINERS: Dict[str, Tuple[Type[View], Sequence[int]]] = {
130+
'SingleFieldTestProfile': (SingleFieldTestProfile, []),
131+
'SmallTestProfile1': (SmallTestProfile1, []),
132+
'SmallTestProfile2': (SmallTestProfile2, []),
133+
'SmallTestProfile3': (SmallTestProfile3, []),
134+
'FixedTestProfile1': (FixedTestProfile1, []),
135+
'FixedTestProfile2': (FixedTestProfile2, []),
136+
'FixedTestProfile3': (FixedTestProfile3, []),
137+
'FixedTestProfile4': (FixedTestProfile4, []),
138+
'VarTestProfile1': (VarTestProfile1, [2]),
139+
'VarTestProfile2': (VarTestProfile2, [2]),
140+
'VarTestProfile3': (VarTestProfile3, [2]),
141+
'ComplexTestProfile1': (ComplexTestProfile1, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]),
142+
'ComplexTestProfile2': (ComplexTestProfile2, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]),
143+
'ComplexTestProfile3': (ComplexTestProfile3, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]),
144+
'ComplexTestProfile4': (ComplexTestProfile4, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]),
145+
'ComplexTestProfile5': (ComplexTestProfile5, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]),
146+
'BitsProfile1': (BitsProfile1, [0, 4 + 1 + 1, 4 + 1 + 1 + 4]),
147+
'BitsProfile2': (BitsProfile2, [0, 4 + 1 + 1, 4 + 1 + 1 + 4]),
148+
'BitsProfile3': (BitsProfile3, [0, 4 + 1 + 1, 4 + 1 + 1 + 4]),
149+
}
150+
151+
152+
def valid_cases():
153+
rng = Random(1234)
154+
for (name, (typ, offsets)) in PRESET_CONTAINERS.items():
155+
for mode in [RandomizationMode.mode_zero, RandomizationMode.mode_max]:
156+
yield f'{name}_{mode.to_name()}', valid_test_case(lambda: container_case_fn(rng, mode, typ))
157+
158+
if len(offsets) == 0:
159+
modes = [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max]
160+
else:
161+
modes = list(RandomizationMode)
162+
163+
for mode in modes:
164+
for variation in range(3):
165+
yield f'{name}_{mode.to_name()}_chaos_{variation}', \
166+
valid_test_case(lambda: container_case_fn(rng, mode, typ, chaos=True))
167+
# Notes: Below is the second wave of iteration, and only the random mode is selected
168+
# for container without offset since ``RandomizationMode.mode_zero`` and ``RandomizationMode.mode_max``
169+
# are deterministic.
170+
modes = [RandomizationMode.mode_random] if len(offsets) == 0 else list(RandomizationMode)
171+
for mode in modes:
172+
for variation in range(10):
173+
yield f'{name}_{mode.to_name()}_{variation}', \
174+
valid_test_case(lambda: container_case_fn(rng, mode, typ))
175+
176+
177+
def mod_offset(b: bytes, offset_index: int, change: Callable[[int], int]):
178+
return b[:offset_index] + \
179+
(change(int.from_bytes(b[offset_index:offset_index + 4], byteorder='little')) & 0xffffffff) \
180+
.to_bytes(length=4, byteorder='little') + \
181+
b[offset_index + 4:]
182+
183+
184+
def invalid_cases():
185+
rng = Random(1234)
186+
for (name, (typ, offsets)) in PRESET_CONTAINERS.items():
187+
# using mode_max_count, so that the extra byte cannot be picked up as normal list content
188+
yield f'{name}_extra_byte', \
189+
invalid_test_case(lambda: serialize(
190+
container_case_fn(rng, RandomizationMode.mode_max_count, typ)) + b'\xff')
191+
192+
if len(offsets) != 0:
193+
# Note: there are many more ways to have invalid offsets,
194+
# these are just example to get clients started looking into hardening ssz.
195+
for mode in [RandomizationMode.mode_random,
196+
RandomizationMode.mode_nil_count,
197+
RandomizationMode.mode_one_count,
198+
RandomizationMode.mode_max_count]:
199+
for index, offset_index in enumerate(offsets):
200+
yield f'{name}_{mode.to_name()}_offset_{offset_index}_plus_one', \
201+
invalid_test_case(lambda: mod_offset(
202+
b=serialize(container_case_fn(rng, mode, typ)),
203+
offset_index=offset_index,
204+
change=lambda x: x + 1
205+
))
206+
yield f'{name}_{mode.to_name()}_offset_{offset_index}_zeroed', \
207+
invalid_test_case(lambda: mod_offset(
208+
b=serialize(container_case_fn(rng, mode, typ)),
209+
offset_index=offset_index,
210+
change=lambda x: 0
211+
))
212+
if index == 0:
213+
yield f'{name}_{mode.to_name()}_offset_{offset_index}_minus_one', \
214+
invalid_test_case(lambda: mod_offset(
215+
b=serialize(container_case_fn(rng, mode, typ)),
216+
offset_index=offset_index,
217+
change=lambda x: x - 1
218+
))
219+
if mode == RandomizationMode.mode_max_count:
220+
serialized = serialize(container_case_fn(rng, mode, typ))
221+
serialized = serialized + serialized[:2]
222+
yield f'{name}_{mode.to_name()}_last_offset_{offset_index}_overflow', \
223+
invalid_test_case(lambda: serialized)
224+
if mode == RandomizationMode.mode_one_count:
225+
serialized = serialize(container_case_fn(rng, mode, typ))
226+
serialized = serialized + serialized[:1]
227+
yield f'{name}_{mode.to_name()}_last_offset_{offset_index}_wrong_byte_length', \
228+
invalid_test_case(lambda: serialized)

0 commit comments

Comments
 (0)