Skip to content

Commit 338b818

Browse files
tcoratgerclaude
andauthored
test(reqresp): add mirrored test_message.py with LIMIT boundary (leanEthereum#991)
Add the missing mirrored unit test module for the req/resp domain message types. Covers protocol identifiers, Status construction and SSZ round-trip with its fixed 80-byte layout, the blocks-by-root and blocks-by-range requests, and the RequestedBlockRoots LIMIT boundary (exactly at the limit accepted, one over the limit rejected with the full error message). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6d4c743 commit 338b818

1 file changed

Lines changed: 124 additions & 0 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""Tests for the req/resp domain message types."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from lean_spec.node.networking.config import MAX_REQUEST_BLOCKS
8+
from lean_spec.node.networking.reqresp.message import (
9+
BLOCKS_BY_RANGE_PROTOCOL_V1,
10+
BLOCKS_BY_ROOT_PROTOCOL_V1,
11+
STATUS_PROTOCOL_V1,
12+
BlocksByRangeRequest,
13+
BlocksByRootRequest,
14+
RequestedBlockRoots,
15+
Status,
16+
)
17+
from lean_spec.node.networking.types import ProtocolId
18+
from lean_spec.spec.forks import Checkpoint, Slot
19+
from lean_spec.spec.ssz import Bytes32, Uint64
20+
from lean_spec.spec.ssz.exceptions import SSZValueError
21+
22+
23+
class TestProtocolIdentifiers:
24+
"""Tests for the request/response protocol identifiers."""
25+
26+
def test_status_protocol_identifier(self) -> None:
27+
"""The Status v1 protocol identifier matches the leanconsensus wire string."""
28+
assert STATUS_PROTOCOL_V1 == ProtocolId("/leanconsensus/req/status/1/ssz_snappy")
29+
30+
def test_blocks_by_root_protocol_identifier(self) -> None:
31+
"""The BlocksByRoot v1 protocol identifier matches the leanconsensus wire string."""
32+
assert BLOCKS_BY_ROOT_PROTOCOL_V1 == ProtocolId(
33+
"/leanconsensus/req/blocks_by_root/1/ssz_snappy"
34+
)
35+
36+
def test_blocks_by_range_protocol_identifier(self) -> None:
37+
"""The BlocksByRange v1 protocol identifier matches the leanconsensus wire string."""
38+
assert BLOCKS_BY_RANGE_PROTOCOL_V1 == ProtocolId(
39+
"/leanconsensus/req/blocks_by_range/1/ssz_snappy"
40+
)
41+
42+
43+
class TestStatus:
44+
"""Tests for the Status handshake message."""
45+
46+
def test_construction_preserves_checkpoints(self) -> None:
47+
"""Status holds the finalized and head checkpoints it was built with."""
48+
finalized_checkpoint = Checkpoint(root=Bytes32(b"\x01" * 32), slot=Slot(7))
49+
head_checkpoint = Checkpoint(root=Bytes32(b"\x02" * 32), slot=Slot(9))
50+
status = Status(finalized=finalized_checkpoint, head=head_checkpoint)
51+
assert status == Status(finalized=finalized_checkpoint, head=head_checkpoint)
52+
53+
def test_ssz_encoding_is_eighty_bytes(self) -> None:
54+
"""Status serializes to the fixed 80-byte layout of two checkpoints."""
55+
status = Status(
56+
finalized=Checkpoint(root=Bytes32(b"\x01" * 32), slot=Slot(7)),
57+
head=Checkpoint(root=Bytes32(b"\x02" * 32), slot=Slot(9)),
58+
)
59+
assert len(status.encode_bytes()) == 80
60+
61+
def test_ssz_roundtrip(self) -> None:
62+
"""Encoding then decoding a Status yields the original message."""
63+
status = Status(
64+
finalized=Checkpoint(root=Bytes32(b"\x01" * 32), slot=Slot(7)),
65+
head=Checkpoint(root=Bytes32(b"\x02" * 32), slot=Slot(9)),
66+
)
67+
assert Status.decode_bytes(status.encode_bytes()) == status
68+
69+
70+
class TestRequestedBlockRoots:
71+
"""Tests for the bounded list of requested block roots."""
72+
73+
def test_limit_matches_max_request_blocks(self) -> None:
74+
"""The list limit equals the configured maximum block request size."""
75+
assert RequestedBlockRoots.LIMIT == MAX_REQUEST_BLOCKS
76+
77+
def test_accepts_list_at_limit(self) -> None:
78+
"""A list filled to exactly the limit is accepted."""
79+
roots = RequestedBlockRoots(data=[Bytes32(b"\x00" * 32)] * MAX_REQUEST_BLOCKS)
80+
assert len(roots) == MAX_REQUEST_BLOCKS
81+
82+
def test_rejects_list_over_limit(self) -> None:
83+
"""A list one element over the limit is rejected with the full message."""
84+
with pytest.raises(SSZValueError) as exception_info:
85+
RequestedBlockRoots(data=[Bytes32(b"\x00" * 32)] * (MAX_REQUEST_BLOCKS + 1))
86+
assert str(exception_info.value) == (
87+
f"RequestedBlockRoots exceeds limit of {MAX_REQUEST_BLOCKS}, "
88+
f"got {MAX_REQUEST_BLOCKS + 1}"
89+
)
90+
91+
92+
class TestBlocksByRootRequest:
93+
"""Tests for the blocks-by-root request message."""
94+
95+
def test_construction_preserves_roots(self) -> None:
96+
"""The request holds the roots it was built with."""
97+
roots = RequestedBlockRoots(data=[Bytes32(b"\x11" * 32), Bytes32(b"\x22" * 32)])
98+
assert BlocksByRootRequest(roots=roots) == BlocksByRootRequest(roots=roots)
99+
100+
def test_ssz_roundtrip(self) -> None:
101+
"""Encoding then decoding a blocks-by-root request yields the original."""
102+
request = BlocksByRootRequest(
103+
roots=RequestedBlockRoots(data=[Bytes32(b"\x11" * 32), Bytes32(b"\x22" * 32)])
104+
)
105+
assert BlocksByRootRequest.decode_bytes(request.encode_bytes()) == request
106+
107+
def test_ssz_roundtrip_empty(self) -> None:
108+
"""An empty blocks-by-root request roundtrips correctly."""
109+
request = BlocksByRootRequest(roots=RequestedBlockRoots(data=[]))
110+
assert BlocksByRootRequest.decode_bytes(request.encode_bytes()) == request
111+
112+
113+
class TestBlocksByRangeRequest:
114+
"""Tests for the blocks-by-range request message."""
115+
116+
def test_construction_preserves_fields(self) -> None:
117+
"""The request holds the start slot and count it was built with."""
118+
request = BlocksByRangeRequest(start_slot=Slot(3), count=Uint64(5))
119+
assert request == BlocksByRangeRequest(start_slot=Slot(3), count=Uint64(5))
120+
121+
def test_ssz_roundtrip(self) -> None:
122+
"""Encoding then decoding a blocks-by-range request yields the original."""
123+
request = BlocksByRangeRequest(start_slot=Slot(3), count=Uint64(5))
124+
assert BlocksByRangeRequest.decode_bytes(request.encode_bytes()) == request

0 commit comments

Comments
 (0)