Skip to content

Commit 292bd1a

Browse files
authored
Merge pull request #811 from yashksaini-coder/feat/804-add-thin-waist-address
✨ Feat: add Thin Waist address validation utilities and integrate into echo example
2 parents b80817b + c9795e3 commit 292bd1a

File tree

12 files changed

+572
-25
lines changed

12 files changed

+572
-25
lines changed

examples/advanced/network_discover.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
Advanced demonstration of Thin Waist address handling.
3+
4+
Run:
5+
python -m examples.advanced.network_discovery
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from multiaddr import Multiaddr
11+
12+
try:
13+
from libp2p.utils.address_validation import (
14+
expand_wildcard_address,
15+
get_available_interfaces,
16+
get_optimal_binding_address,
17+
)
18+
except ImportError:
19+
# Fallbacks if utilities are missing
20+
def get_available_interfaces(port: int, protocol: str = "tcp"):
21+
return [Multiaddr(f"/ip4/0.0.0.0/{protocol}/{port}")]
22+
23+
def expand_wildcard_address(addr: Multiaddr, port: int | None = None):
24+
if port is None:
25+
return [addr]
26+
addr_str = str(addr).rsplit("/", 1)[0]
27+
return [Multiaddr(addr_str + f"/{port}")]
28+
29+
def get_optimal_binding_address(port: int, protocol: str = "tcp"):
30+
return Multiaddr(f"/ip4/0.0.0.0/{protocol}/{port}")
31+
32+
33+
def main() -> None:
34+
port = 8080
35+
interfaces = get_available_interfaces(port)
36+
print(f"Discovered interfaces for port {port}:")
37+
for a in interfaces:
38+
print(f" - {a}")
39+
40+
wildcard_v4 = Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")
41+
expanded_v4 = expand_wildcard_address(wildcard_v4)
42+
print("\nExpanded IPv4 wildcard:")
43+
for a in expanded_v4:
44+
print(f" - {a}")
45+
46+
wildcard_v6 = Multiaddr(f"/ip6/::/tcp/{port}")
47+
expanded_v6 = expand_wildcard_address(wildcard_v6)
48+
print("\nExpanded IPv6 wildcard:")
49+
for a in expanded_v6:
50+
print(f" - {a}")
51+
52+
print("\nOptimal binding address heuristic result:")
53+
print(f" -> {get_optimal_binding_address(port)}")
54+
55+
override_port = 9000
56+
overridden = expand_wildcard_address(wildcard_v4, port=override_port)
57+
print(f"\nPort override expansion to {override_port}:")
58+
for a in overridden:
59+
print(f" - {a}")
60+
61+
62+
if __name__ == "__main__":
63+
main()

examples/echo/echo.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import argparse
2+
import random
3+
import secrets
24

35
import multiaddr
46
import trio
@@ -12,40 +14,54 @@
1214
from libp2p.custom_types import (
1315
TProtocol,
1416
)
17+
from libp2p.network.stream.exceptions import (
18+
StreamEOF,
19+
)
1520
from libp2p.network.stream.net_stream import (
1621
INetStream,
1722
)
1823
from libp2p.peer.peerinfo import (
1924
info_from_p2p_addr,
2025
)
26+
from libp2p.utils.address_validation import (
27+
find_free_port,
28+
get_available_interfaces,
29+
)
2130

2231
PROTOCOL_ID = TProtocol("/echo/1.0.0")
2332
MAX_READ_LEN = 2**32 - 1
2433

2534

2635
async def _echo_stream_handler(stream: INetStream) -> None:
27-
# Wait until EOF
28-
msg = await stream.read(MAX_READ_LEN)
29-
await stream.write(msg)
30-
await stream.close()
36+
try:
37+
peer_id = stream.muxed_conn.peer_id
38+
print(f"Received connection from {peer_id}")
39+
# Wait until EOF
40+
msg = await stream.read(MAX_READ_LEN)
41+
print(f"Echoing message: {msg.decode('utf-8')}")
42+
await stream.write(msg)
43+
except StreamEOF:
44+
print("Stream closed by remote peer.")
45+
except Exception as e:
46+
print(f"Error in echo handler: {e}")
47+
finally:
48+
await stream.close()
3149

3250

3351
async def run(port: int, destination: str, seed: int | None = None) -> None:
34-
listen_addr = multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")
52+
if port <= 0:
53+
port = find_free_port()
54+
listen_addr = get_available_interfaces(port)
3555

3656
if seed:
37-
import random
38-
3957
random.seed(seed)
4058
secret_number = random.getrandbits(32 * 8)
4159
secret = secret_number.to_bytes(length=32, byteorder="big")
4260
else:
43-
import secrets
44-
4561
secret = secrets.token_bytes(32)
4662

4763
host = new_host(key_pair=create_new_key_pair(secret))
48-
async with host.run(listen_addrs=[listen_addr]), trio.open_nursery() as nursery:
64+
async with host.run(listen_addrs=listen_addr), trio.open_nursery() as nursery:
4965
# Start the peer-store cleanup task
5066
nursery.start_soon(host.get_peerstore().start_cleanup_task, 60)
5167

@@ -54,10 +70,15 @@ async def run(port: int, destination: str, seed: int | None = None) -> None:
5470
if not destination: # its the server
5571
host.set_stream_handler(PROTOCOL_ID, _echo_stream_handler)
5672

73+
# Print all listen addresses with peer ID (JS parity)
74+
print("Listener ready, listening on:\n")
75+
peer_id = host.get_id().to_string()
76+
for addr in listen_addr:
77+
print(f"{addr}/p2p/{peer_id}")
78+
5779
print(
58-
"Run this from the same folder in another console:\n\n"
59-
f"echo-demo "
60-
f"-d {host.get_addrs()[0]}\n"
80+
"\nRun this from the same folder in another console:\n\n"
81+
f"echo-demo -d {host.get_addrs()[0]}\n"
6182
)
6283
print("Waiting for incoming connections...")
6384
await trio.sleep_forever()

examples/pubsub/pubsub.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import argparse
22
import logging
3-
import socket
43

54
import base58
65
import multiaddr
@@ -31,6 +30,9 @@
3130
from libp2p.tools.async_service.trio_service import (
3231
background_trio_service,
3332
)
33+
from libp2p.utils.address_validation import (
34+
find_free_port,
35+
)
3436

3537
# Configure logging
3638
logging.basicConfig(
@@ -77,13 +79,6 @@ async def publish_loop(pubsub, topic, termination_event):
7779
await trio.sleep(1) # Avoid tight loop on error
7880

7981

80-
def find_free_port():
81-
"""Find a free port on localhost."""
82-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
83-
s.bind(("", 0)) # Bind to a free port provided by the OS
84-
return s.getsockname()[1]
85-
86-
8782
async def monitor_peer_topics(pubsub, nursery, termination_event):
8883
"""
8984
Monitor for new topics that peers are subscribed to and

libp2p/network/swarm.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,11 @@ async def listen(self, *multiaddrs: Multiaddr) -> bool:
249249
# We need to wait until `self.listener_nursery` is created.
250250
await self.event_listener_nursery_created.wait()
251251

252+
success_count = 0
252253
for maddr in multiaddrs:
253254
if str(maddr) in self.listeners:
254-
return True
255+
success_count += 1
256+
continue
255257

256258
async def conn_handler(
257259
read_write_closer: ReadWriteCloser, maddr: Multiaddr = maddr
@@ -302,13 +304,14 @@ async def conn_handler(
302304
# Call notifiers since event occurred
303305
await self.notify_listen(maddr)
304306

305-
return True
307+
success_count += 1
308+
logger.debug("successfully started listening on: %s", maddr)
306309
except OSError:
307310
# Failed. Continue looping.
308311
logger.debug("fail to listen on: %s", maddr)
309312

310-
# No maddr succeeded
311-
return False
313+
# Return true if at least one address succeeded
314+
return success_count > 0
312315

313316
async def close(self) -> None:
314317
"""

libp2p/utils/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
get_agent_version,
1616
)
1717

18+
from libp2p.utils.address_validation import (
19+
get_available_interfaces,
20+
get_optimal_binding_address,
21+
expand_wildcard_address,
22+
find_free_port,
23+
)
24+
1825
__all__ = [
1926
"decode_uvarint_from_stream",
2027
"encode_delim",
@@ -26,4 +33,8 @@
2633
"decode_varint_from_bytes",
2734
"decode_varint_with_size",
2835
"read_length_prefixed_protobuf",
36+
"get_available_interfaces",
37+
"get_optimal_binding_address",
38+
"expand_wildcard_address",
39+
"find_free_port",
2940
]

0 commit comments

Comments
 (0)