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: 4 additions & 6 deletions src/lean_spec/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from lean_spec.subspecs.containers.slot import Slot
from lean_spec.subspecs.forkchoice import Store
from lean_spec.subspecs.genesis import GenesisConfig
from lean_spec.subspecs.networking import compute_subnet_id
from lean_spec.subspecs.networking.client import LiveNetworkEventSource
from lean_spec.subspecs.networking.gossipsub import GossipTopic
from lean_spec.subspecs.networking.reqresp.message import Status
Expand Down Expand Up @@ -98,13 +97,12 @@ def resolve_bootnode(bootnode: str) -> str:
if not enr.verify_signature():
raise ValueError(f"ENR signature verification failed: {enr}")

# ENR.multiaddr() returns None when the record lacks IP or TCP port.
# ENR.multiaddr() returns None when the record lacks IP or UDP port.
#
# This happens with discovery-only ENRs that only contain UDP info.
# We require TCP for libp2p connections.
# We require UDP for QUIC connections.
multiaddr = enr.multiaddr()
if multiaddr is None:
raise ValueError(f"ENR has no TCP connection info: {enr}")
raise ValueError(f"ENR has no UDP connection info: {enr}")
return multiaddr

# Already a multiaddr string. Pass through without validation.
Expand Down Expand Up @@ -495,7 +493,7 @@ async def run_node(
subnet_id = 0
logger.info("No local validator id; subscribing to attestation subnet %d", subnet_id)
else:
subnet_id = compute_subnet_id(validator_id, ATTESTATION_COMMITTEE_COUNT)
subnet_id = validator_id.compute_subnet_id(ATTESTATION_COMMITTEE_COUNT)
attestation_subnet_topic = str(GossipTopic.attestation_subnet(GOSSIP_FORK_DIGEST, subnet_id))
event_source.subscribe_gossip_topic(attestation_subnet_topic)
logger.info("Subscribed to gossip topics: %s, %s", block_topic, attestation_subnet_topic)
Expand Down
11 changes: 11 additions & 0 deletions src/lean_spec/subspecs/containers/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ def is_valid(self, num_validators: int) -> bool:
"""Check if this index is within valid bounds for a registry of given size."""
return int(self) < num_validators

def compute_subnet_id(self, num_committees: "int | Uint64") -> int:
"""Compute the attestation subnet id for this validator.

Args:
num_committees: Positive number of committees.

Returns:
An integer subnet id in 0..(num_committees-1).
"""
return int(self) % int(num_committees)


class ValidatorIndices(SSZList[ValidatorIndex]):
"""List of validator indices up to registry limit."""
Expand Down
15 changes: 7 additions & 8 deletions src/lean_spec/subspecs/forkchoice/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
from lean_spec.subspecs.containers.attestation.attestation import SignedAggregatedAttestation
from lean_spec.subspecs.containers.block import BlockLookup
from lean_spec.subspecs.containers.slot import Slot
from lean_spec.subspecs.networking import compute_subnet_id
from lean_spec.subspecs.ssz.hash import hash_tree_root
from lean_spec.subspecs.xmss.aggregation import (
AggregatedSignatureProof,
Expand Down Expand Up @@ -403,10 +402,10 @@ def on_gossip_attestation(

if is_aggregator:
assert self.validator_id is not None, "Current validator ID must be set for aggregation"
current_validator_subnet = compute_subnet_id(
self.validator_id, ATTESTATION_COMMITTEE_COUNT
current_validator_subnet = self.validator_id.compute_subnet_id(
ATTESTATION_COMMITTEE_COUNT
)
attester_subnet = compute_subnet_id(validator_id, ATTESTATION_COMMITTEE_COUNT)
attester_subnet = validator_id.compute_subnet_id(ATTESTATION_COMMITTEE_COUNT)
if current_validator_subnet != attester_subnet:
# Not part of our committee; ignore for committee aggregation.
pass
Expand Down Expand Up @@ -661,11 +660,11 @@ def on_block(
# as the current validator.
if self.validator_id is not None:
proposer_validator_id = proposer_attestation.validator_id
proposer_subnet_id = compute_subnet_id(
proposer_validator_id, ATTESTATION_COMMITTEE_COUNT
proposer_subnet_id = proposer_validator_id.compute_subnet_id(
ATTESTATION_COMMITTEE_COUNT
)
current_validator_subnet_id = compute_subnet_id(
self.validator_id, ATTESTATION_COMMITTEE_COUNT
current_validator_subnet_id = self.validator_id.compute_subnet_id(
ATTESTATION_COMMITTEE_COUNT
)
if proposer_subnet_id == current_validator_subnet_id:
proposer_sig_key = SignatureKey(
Expand Down
2 changes: 0 additions & 2 deletions src/lean_spec/subspecs/networking/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
PeerDisconnectedEvent,
PeerStatusEvent,
)
from .subnet import compute_subnet_id
from .transport import PeerId
from .types import DomainType, ForkDigest, ProtocolId

Expand Down Expand Up @@ -74,5 +73,4 @@
"ForkDigest",
"PeerId",
"ProtocolId",
"compute_subnet_id",
]
24 changes: 5 additions & 19 deletions src/lean_spec/subspecs/networking/enr/enr.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,36 +128,24 @@ def ip6(self) -> str | None:
return ":".join(ip_bytes[i : i + 2].hex() for i in range(0, 16, 2))
return None

@property
def tcp_port(self) -> int | None:
"""TCP port (applies to both IPv4 and IPv6 unless tcp6 is set)."""
port = self.get(keys.TCP)
return int.from_bytes(port, "big") if port else None

@property
def udp_port(self) -> int | None:
"""UDP port for discovery (applies to both unless udp6 is set)."""
port = self.get(keys.UDP)
return int.from_bytes(port, "big") if port else None

@property
def tcp6_port(self) -> int | None:
"""IPv6-specific TCP port. Falls back to tcp_port if not set."""
port = self.get(keys.TCP6)
return int.from_bytes(port, "big") if port else None

@property
def udp6_port(self) -> int | None:
"""IPv6-specific UDP port. Falls back to udp_port if not set."""
port = self.get(keys.UDP6)
return int.from_bytes(port, "big") if port else None

def multiaddr(self) -> Multiaddr | None:
"""Construct multiaddress from endpoint info."""
if self.ip4 and self.tcp_port:
return f"/ip4/{self.ip4}/tcp/{self.tcp_port}"
if self.ip6 and self.tcp_port:
return f"/ip6/{self.ip6}/tcp/{self.tcp_port}"
"""Construct QUIC multiaddress from endpoint info."""
if self.ip4 and self.udp_port:
return f"/ip4/{self.ip4}/udp/{self.udp_port}/quic-v1"
if self.ip6 and self.udp_port:
return f"/ip6/{self.ip6}/udp/{self.udp_port}/quic-v1"
return None

@property
Expand Down Expand Up @@ -340,8 +328,6 @@ def __str__(self) -> str:
parts = [f"ENR(seq={self.seq}"]
if self.ip4:
parts.append(f"ip={self.ip4}")
if self.tcp_port:
parts.append(f"tcp={self.tcp_port}")
if self.udp_port:
parts.append(f"udp={self.udp_port}")
if eth2 := self.eth2_data:
Expand Down
6 changes: 0 additions & 6 deletions src/lean_spec/subspecs/networking/enr/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,12 @@
IP: Final[EnrKey] = "ip"
"""IPv4 address (4 bytes)."""

TCP: Final[EnrKey] = "tcp"
"""TCP port (big-endian integer)."""

UDP: Final[EnrKey] = "udp"
"""UDP port for discovery (big-endian integer)."""

IP6: Final[EnrKey] = "ip6"
"""IPv6 address (16 bytes)."""

TCP6: Final[EnrKey] = "tcp6"
"""IPv6-specific TCP port."""

UDP6: Final[EnrKey] = "udp6"
"""IPv6-specific UDP port."""

Expand Down
23 changes: 0 additions & 23 deletions src/lean_spec/subspecs/networking/subnet.py

This file was deleted.

3 changes: 1 addition & 2 deletions src/lean_spec/subspecs/node/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
from lean_spec.subspecs.containers.validator import ValidatorIndex
from lean_spec.subspecs.forkchoice import Store
from lean_spec.subspecs.networking import NetworkEventSource, NetworkService
from lean_spec.subspecs.networking.subnet import compute_subnet_id
from lean_spec.subspecs.ssz.hash import hash_tree_root
from lean_spec.subspecs.sync import BlockCache, NetworkRequester, PeerManager, SyncService
from lean_spec.subspecs.validator import ValidatorRegistry, ValidatorService
Expand Down Expand Up @@ -261,7 +260,7 @@ def from_genesis(cls, config: NodeConfig) -> Node:
# Create a wrapper for publish_attestation that computes the subnet_id
# from the validator_id in the attestation
async def publish_attestation_wrapper(attestation: SignedAttestation) -> None:
subnet_id = compute_subnet_id(attestation.validator_id, ATTESTATION_COMMITTEE_COUNT)
subnet_id = attestation.validator_id.compute_subnet_id(ATTESTATION_COMMITTEE_COUNT)
await network_service.publish_attestation(attestation, subnet_id)

validator_service = ValidatorService(
Expand Down
Loading
Loading