Skip to content

Commit 49f70c7

Browse files
authored
bitfields: capability to return a list from a slice key (leanEthereum#180)
* bitfields: capability to return a list from a slice key * fix MAX_TRIES in test config * primitive touchups
1 parent 1a3c58c commit 49f70c7

7 files changed

Lines changed: 98 additions & 9 deletions

File tree

src/lean_spec/subspecs/containers/block/block.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def verify_signatures(self, parent_state: "State") -> bool:
145145
# This creates a single list containing both:
146146
# 1. Block body attestations (from other validators)
147147
# 2. Proposer attestation (from the block producer)
148-
all_attestations = list(block.body.attestations) + [self.message.proposer_attestation]
148+
all_attestations = block.body.attestations + [self.message.proposer_attestation]
149149

150150
# Verify signature count matches attestation count
151151
#

src/lean_spec/subspecs/containers/state/state.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,12 @@ def process_block_header(self, block: Block) -> "State":
257257

258258
# Build new historical hashes list
259259
new_historical_hashes_data = (
260-
list(self.historical_block_hashes) + [parent_root] + ([ZERO_HASH] * num_empty_slots)
260+
self.historical_block_hashes + [parent_root] + ([ZERO_HASH] * num_empty_slots)
261261
)
262262

263263
# Build new justified slots list
264264
new_justified_slots_data = (
265-
list(self.justified_slots)
265+
self.justified_slots
266266
+ [Boolean(is_genesis_parent)]
267267
+ ([Boolean(False)] * num_empty_slots)
268268
)
@@ -354,11 +354,9 @@ def process_attestations(
354354
# - each segment corresponds to one block root,
355355
# - each segment has length equal to the number of validators,
356356
# - and the ordering of block roots is preserved.
357-
flat_justifications = list(self.justifications_validators)
358-
359357
justifications = (
360358
{
361-
root: flat_justifications[
359+
root: self.justifications_validators[
362360
i * self.validators.count : (i + 1) * self.validators.count
363361
]
364362
for i, root in enumerate(self.justifications_roots)
@@ -370,7 +368,7 @@ def process_attestations(
370368
# Track state changes to be applied at the end
371369
latest_justified = self.latest_justified
372370
latest_finalized = self.latest_finalized
373-
justified_slots = list(self.justified_slots)
371+
justified_slots = self.justified_slots
374372

375373
# Process each attestation in the block.
376374
for attestation in attestations:

src/lean_spec/subspecs/xmss/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def SIGNATURE_LEN_BYTES(self) -> int: # noqa: N802
131131
BASE=4,
132132
FINAL_LAYER=6,
133133
TARGET_SUM=6,
134-
MAX_TRIES=1_000,
134+
MAX_TRIES=100_000,
135135
PARAMETER_LEN=5,
136136
TWEAK_LEN_FE=2,
137137
MSG_LEN_FE=9,

src/lean_spec/types/bitfields.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
Any,
2323
ClassVar,
2424
Tuple,
25+
overload,
2526
)
2627

2728
from pydantic import Field, field_validator
@@ -157,10 +158,34 @@ def _validate_list_data(cls, v: Any) -> Tuple[Boolean, ...]:
157158
except Exception as e:
158159
raise ValueError(f"Cannot convert elements to Boolean: {e}") from e
159160

160-
def __getitem__(self, key: int | slice) -> Boolean | tuple[Boolean, ...]:
161+
@overload
162+
def __getitem__(self, key: int) -> Boolean: ...
163+
164+
@overload
165+
def __getitem__(self, key: slice) -> list[Boolean]: ...
166+
167+
def __getitem__(self, key: int | slice) -> Boolean | list[Boolean]:
161168
"""Get a bit by index or slice."""
169+
if isinstance(key, slice):
170+
return list(self.data[key])
162171
return self.data[key]
163172

173+
def __setitem__(self, key: int, value: bool | Boolean) -> None:
174+
"""Set a bit by index."""
175+
new_data = list(self.data)
176+
new_data[key] = Boolean(value)
177+
object.__setattr__(self, "data", tuple(new_data))
178+
179+
def __add__(self, other: Any) -> Self:
180+
"""Concatenate this bitlist with another sequence."""
181+
if isinstance(other, BaseBitlist):
182+
new_data = self.data + other.data
183+
elif isinstance(other, (list, tuple)):
184+
new_data = self.data + tuple(Boolean(b) for b in other)
185+
else:
186+
return NotImplemented
187+
return type(self)(data=new_data)
188+
164189
@classmethod
165190
def is_fixed_size(cls) -> bool:
166191
"""A Bitlist is never fixed-size (length varies from 0 to LIMIT)."""

src/lean_spec/types/collections.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ def _validate_list_data(cls, v: Any) -> Tuple[SSZType, ...]:
227227

228228
return tuple(typed_values)
229229

230+
def __add__(self, other: Any) -> Self:
231+
"""Concatenate this list with another sequence."""
232+
if isinstance(other, SSZList):
233+
new_data = self.data + other.data
234+
elif isinstance(other, (list, tuple)):
235+
new_data = self.data + tuple(other)
236+
else:
237+
return NotImplemented
238+
return type(self)(data=new_data)
239+
230240
@classmethod
231241
def is_fixed_size(cls) -> bool:
232242
"""An SSZList is never fixed-size (length varies from 0 to LIMIT)."""

tests/lean_spec/types/test_bitfields.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,41 @@ class Bitlist8(BaseBitlist):
166166
with pytest.raises(ValidationError):
167167
model(value=invalid_value)
168168

169+
def test_add_with_list(self) -> None:
170+
"""Tests concatenating a Bitlist with a regular list."""
171+
172+
class Bitlist8(BaseBitlist):
173+
LIMIT = 8
174+
175+
bitlist = Bitlist8(data=[True, False, True])
176+
result = bitlist + [False, True]
177+
assert len(result) == 5
178+
assert list(result.data) == [True, False, True, False, True]
179+
assert isinstance(result, Bitlist8)
180+
181+
def test_add_with_bitlist(self) -> None:
182+
"""Tests concatenating two Bitlists of the same type."""
183+
184+
class Bitlist8(BaseBitlist):
185+
LIMIT = 8
186+
187+
bitlist1 = Bitlist8(data=[True, False])
188+
bitlist2 = Bitlist8(data=[True, True])
189+
result = bitlist1 + bitlist2
190+
assert len(result) == 4
191+
assert list(result.data) == [True, False, True, True]
192+
assert isinstance(result, Bitlist8)
193+
194+
def test_add_exceeding_limit_raises_error(self) -> None:
195+
"""Tests that concatenating beyond the limit raises an error."""
196+
197+
class Bitlist4(BaseBitlist):
198+
LIMIT = 4
199+
200+
bitlist = Bitlist4(data=[True, False, True])
201+
with pytest.raises(ValueError, match="cannot contain more than 4 bits"):
202+
bitlist + [False, True]
203+
169204

170205
class TestBitfieldSerialization:
171206
"""Tests the `encode_bytes` and `decode_bytes` methods for bitfields."""

tests/lean_spec/types/test_collections.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,27 @@ def test_extend_over_limit_raises_error(self) -> None:
287287
with pytest.raises(ValueError, match="cannot contain more than 4 elements"):
288288
BooleanList4(data=[True, False, True, False, True])
289289

290+
def test_add_with_list(self) -> None:
291+
"""Tests concatenating an SSZList with a regular list."""
292+
list1 = Uint8List10(data=[1, 2, 3])
293+
result = list1 + [4, 5]
294+
assert list(result) == [Uint8(1), Uint8(2), Uint8(3), Uint8(4), Uint8(5)]
295+
assert isinstance(result, Uint8List10)
296+
297+
def test_add_with_sszlist(self) -> None:
298+
"""Tests concatenating two SSZLists of the same type."""
299+
list1 = Uint8List10(data=[1, 2])
300+
list2 = Uint8List10(data=[3, 4])
301+
result = list1 + list2
302+
assert list(result) == [Uint8(1), Uint8(2), Uint8(3), Uint8(4)]
303+
assert isinstance(result, Uint8List10)
304+
305+
def test_add_exceeding_limit_raises_error(self) -> None:
306+
"""Tests that concatenating beyond the limit raises an error."""
307+
list1 = Uint8List4(data=[1, 2, 3])
308+
with pytest.raises(ValueError, match="cannot contain more than 4 elements"):
309+
list1 + [4, 5]
310+
290311

291312
class TestSSZVectorSerialization:
292313
"""Tests SSZ serialization and deserialization for the SSZVector type."""

0 commit comments

Comments
 (0)