Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/lean_spec/subspecs/networking/gossipsub/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def decode(cls, data: bytes) -> ControlGraft:


@dataclass(slots=True)
class PeerInfo:
class PrunePeerInfo:
"""Peer information for PRUNE peer exchange."""

peer_id: bytes = b""
Expand All @@ -370,7 +370,7 @@ def encode(self) -> bytes:
return bytes(result)

@classmethod
def decode(cls, data: bytes) -> PeerInfo:
def decode(cls, data: bytes) -> PrunePeerInfo:
"""Decode from protobuf."""
peer_id = b""
signed_peer_record = b""
Expand Down Expand Up @@ -401,7 +401,7 @@ class ControlPrune:
topic_id: str = ""
"""Topic being pruned from."""

peers: list[PeerInfo] = field(default_factory=list)
peers: list[PrunePeerInfo] = field(default_factory=list)
"""Peer exchange - alternative peers for the topic (v1.1)."""

backoff: int = 0
Expand All @@ -422,7 +422,7 @@ def encode(self) -> bytes:
def decode(cls, data: bytes) -> ControlPrune:
"""Decode from protobuf."""
topic_id = ""
peers: list[PeerInfo] = []
peers: list[PrunePeerInfo] = []
backoff = 0
pos = 0

Expand All @@ -435,7 +435,7 @@ def decode(cls, data: bytes) -> ControlPrune:
pos += length
elif field_num == 2 and wire_type == WIRE_TYPE_LENGTH_DELIMITED:
length, pos = _decode_length_at(data, pos)
peers.append(PeerInfo.decode(data[pos : pos + length]))
peers.append(PrunePeerInfo.decode(data[pos : pos + length]))
pos += length
elif field_num == 3 and wire_type == WIRE_TYPE_VARINT:
backoff, pos = _decode_varint_at(data, pos)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,15 @@
from __future__ import annotations

from dataclasses import dataclass, field
from enum import IntEnum, auto
from time import time
from typing import TYPE_CHECKING

from ..transport import PeerId
from ..types import ConnectionState, Multiaddr
from .transport import PeerId
from .types import ConnectionState, Direction, Multiaddr

if TYPE_CHECKING:
from ..enr import ENR
from ..reqresp import Status


class Direction(IntEnum):
"""
Direction of a peer connection.

Indicates whether:
- we initiated the connection (outbound) or
- the peer connected to us (inbound).
"""

INBOUND = auto()
"""Peer initiated the connection to us."""

OUTBOUND = auto()
"""We initiated the connection to the peer."""
from .enr import ENR
from .reqresp import Status


@dataclass
Expand Down
8 changes: 0 additions & 8 deletions src/lean_spec/subspecs/networking/peer/__init__.py

This file was deleted.

2 changes: 1 addition & 1 deletion src/lean_spec/subspecs/networking/service/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from lean_spec.subspecs.containers.attestation import SignedAggregatedAttestation, SignedAttestation
from lean_spec.subspecs.networking.client.event_source import LiveNetworkEventSource
from lean_spec.subspecs.networking.gossipsub.topic import GossipTopic
from lean_spec.subspecs.networking.peer.info import PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.types import ConnectionState

from .events import (
Expand Down
16 changes: 16 additions & 0 deletions src/lean_spec/subspecs/networking/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@
"""Multiaddress string, e.g. ``/ip4/192.168.1.1/udp/9000/quic-v1``."""


class Direction(IntEnum):
"""
Direction of a peer connection.

Indicates whether:
- we initiated the connection (outbound) or
- the peer connected to us (inbound).
"""

INBOUND = auto()
"""Peer initiated the connection to us."""

OUTBOUND = auto()
"""We initiated the connection to the peer."""


class ConnectionState(IntEnum):
"""
Peer connection state machine.
Expand Down
2 changes: 1 addition & 1 deletion src/lean_spec/subspecs/sync/peer_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from lean_spec.subspecs.containers.slot import Slot
from lean_spec.subspecs.networking import PeerId
from lean_spec.subspecs.networking.peer.info import PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.reqresp.message import Status

from .config import MAX_CONCURRENT_REQUESTS
Expand Down
2 changes: 1 addition & 1 deletion tests/interop/helpers/node_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from lean_spec.subspecs.forkchoice import Store
from lean_spec.subspecs.networking import PeerId
from lean_spec.subspecs.networking.client import LiveNetworkEventSource
from lean_spec.subspecs.networking.peer.info import PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.reqresp.message import Status
from lean_spec.subspecs.networking.types import ConnectionState
from lean_spec.subspecs.node import Node, NodeConfig
Expand Down
2 changes: 1 addition & 1 deletion tests/lean_spec/helpers/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from lean_spec.subspecs.forkchoice import Store
from lean_spec.subspecs.koalabear import Fp
from lean_spec.subspecs.networking import PeerId
from lean_spec.subspecs.networking.peer.info import PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.reqresp.message import Status
from lean_spec.subspecs.networking.types import ConnectionState
from lean_spec.subspecs.ssz.hash import hash_tree_root
Expand Down
2 changes: 1 addition & 1 deletion tests/lean_spec/subspecs/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest

from lean_spec.subspecs.networking import PeerId
from lean_spec.subspecs.networking.peer.info import PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.types import ConnectionState


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ async def test_mesh_rebalances_after_disconnect(

# D_low=3 (same as D): losing even 1 mesh peer drops below D_low.
# 10 nodes, remove 5: each remaining node had ~3 mesh peers from 9,
# with 5 removed it's near-certain at least one mesh peer was removed.
# so it's very likely at least one mesh peer was removed.
params = fast_params(heartbeat_interval_secs=999, d_low=3)
await network.create_nodes(10, params)
await network.start_all()
await network.connect_full()
await network.subscribe_all(TOPIC)
await network.stabilize_mesh(TOPIC, rounds=3)

# Remove 5 nodes. Heavy removal guarantees mesh disruption.
# Remove 5 nodes. Heavy removal disrupts meshes in most cases.
removed = network.nodes[5:]
for node in removed:
await node.stop()
Expand All @@ -129,9 +129,6 @@ async def test_mesh_rebalances_after_disconnect(

network.nodes = network.nodes[:5]

# Precondition: peer removal pushed at least one mesh out of bounds.
assert not _all_meshes_in_bounds(network, params, TOPIC)

# Heartbeats detect under-sized meshes and GRAFT replacement peers.
await network.stabilize_mesh(TOPIC, rounds=5)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,16 @@ async def test_peer_churn(
await new_node.connect_to(existing)

# Heartbeat rounds let the mesh absorb new peers via GRAFT.
# Under CPU pressure, a fixed number of rounds may not suffice.
# Retry until all meshes converge or a timeout is hit.
await asyncio.sleep(0.1)
await network.stabilize_mesh(TOPIC, rounds=5)
max_rounds = 20
for _ in range(max_rounds):
await network.stabilize_mesh(TOPIC, rounds=1)
if all(
params.d_low <= node.get_mesh_size(TOPIC) <= params.d_high for node in network.nodes
):
break

for node in network.nodes:
size = node.get_mesh_size(TOPIC)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,33 @@
ControlMessage,
ControlPrune,
Message,
PeerInfo,
ProtobufDecodeError,
PrunePeerInfo,
SubOpts,
_skip_field,
encode_bytes,
encode_tag,
)


class TestPeerInfoRoundtrip:
"""Tests for PeerInfo protobuf encoding/decoding."""
class TestPrunePeerInfoRoundtrip:
"""Tests for PrunePeerInfo protobuf encoding/decoding."""

def test_peer_info_with_both_fields(self) -> None:
"""PeerInfo roundtrips with both peer_id and signed_peer_record."""
info = PeerInfo(peer_id=b"peer123", signed_peer_record=b"record456")
assert PeerInfo.decode(info.encode()) == info
"""PrunePeerInfo roundtrips with both peer_id and signed_peer_record."""
info = PrunePeerInfo(peer_id=b"peer123", signed_peer_record=b"record456")
assert PrunePeerInfo.decode(info.encode()) == info

def test_peer_info_peer_id_only(self) -> None:
"""PeerInfo roundtrips with only peer_id."""
info = PeerInfo(peer_id=b"peerOnly")
assert PeerInfo.decode(info.encode()) == info
"""PrunePeerInfo roundtrips with only peer_id."""
info = PrunePeerInfo(peer_id=b"peerOnly")
assert PrunePeerInfo.decode(info.encode()) == info

def test_peer_info_empty(self) -> None:
"""Empty PeerInfo produces empty encoding."""
info = PeerInfo()
"""Empty PrunePeerInfo produces empty encoding."""
info = PrunePeerInfo()
assert info.encode() == b""
assert PeerInfo.decode(b"") == PeerInfo()
assert PrunePeerInfo.decode(b"") == PrunePeerInfo()


class TestPruneWithPeerExchange:
Expand All @@ -53,8 +53,8 @@ def test_prune_with_peers(self) -> None:
prune = ControlPrune(
topic_id="/topic",
peers=[
PeerInfo(peer_id=b"alt1", signed_peer_record=b"rec1"),
PeerInfo(peer_id=b"alt2"),
PrunePeerInfo(peer_id=b"alt1", signed_peer_record=b"rec1"),
PrunePeerInfo(peer_id=b"alt2"),
],
backoff=120,
)
Expand Down
4 changes: 2 additions & 2 deletions tests/lean_spec/subspecs/networking/test_peer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from lean_spec.subspecs.networking import PeerId
from lean_spec.subspecs.networking.enr import ENR
from lean_spec.subspecs.networking.enr.eth2 import FAR_FUTURE_EPOCH
from lean_spec.subspecs.networking.peer import Direction, PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.reqresp import Status
from lean_spec.subspecs.networking.types import ConnectionState, GoodbyeReason
from lean_spec.subspecs.networking.types import ConnectionState, Direction, GoodbyeReason
from lean_spec.types import Bytes32, Bytes64, Uint64


Expand Down
2 changes: 1 addition & 1 deletion tests/lean_spec/subspecs/sync/test_backfill_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from lean_spec.subspecs.containers.slot import Slot
from lean_spec.subspecs.containers.validator import ValidatorIndex
from lean_spec.subspecs.networking import PeerId
from lean_spec.subspecs.networking.peer.info import PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.types import ConnectionState
from lean_spec.subspecs.sync.backfill_sync import BackfillSync
from lean_spec.subspecs.sync.block_cache import BlockCache
Expand Down
2 changes: 1 addition & 1 deletion tests/lean_spec/subspecs/sync/test_peer_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from lean_spec.subspecs.containers.checkpoint import Checkpoint
from lean_spec.subspecs.containers.slot import Slot
from lean_spec.subspecs.networking import PeerId
from lean_spec.subspecs.networking.peer.info import PeerInfo
from lean_spec.subspecs.networking.peer import PeerInfo
from lean_spec.subspecs.networking.reqresp.message import Status
from lean_spec.subspecs.networking.types import ConnectionState
from lean_spec.subspecs.sync.config import MAX_CONCURRENT_REQUESTS
Expand Down
Loading