Skip to content

Commit fd22cd6

Browse files
committed
Merge branch 'main' of https://github.com/Nkovaturient/py-libp2p into feat/add-webrtc-transport
2 parents 6920d5d + e6a355d commit fd22cd6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+3361
-200
lines changed

docs/release_notes.rst

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,65 @@ Release Notes
33

44
.. towncrier release notes start
55
6+
py-libp2p v0.2.9 (2025-07-09)
7+
-----------------------------
8+
9+
Breaking Changes
10+
~~~~~~~~~~~~~~~~
11+
12+
- Reordered the arguments to ``upgrade_security`` to place ``is_initiator`` before ``peer_id``, and made ``peer_id`` optional.
13+
This allows the method to reflect the fact that peer identity is not required for inbound connections. (`#681 <https://github.com/libp2p/py-libp2p/issues/681>`__)
14+
15+
16+
Bugfixes
17+
~~~~~~~~
18+
19+
- Add timeout wrappers in:
20+
1. ``multiselect.py``: ``negotiate`` function
21+
2. ``multiselect_client.py``: ``select_one_of`` , ``query_multistream_command`` functions
22+
to prevent indefinite hangs when a remote peer does not respond. (`#696 <https://github.com/libp2p/py-libp2p/issues/696>`__)
23+
- Align stream creation logic with yamux specification (`#701 <https://github.com/libp2p/py-libp2p/issues/701>`__)
24+
- Fixed an issue in ``Pubsub`` where async validators were not handled reliably under concurrency. Now uses a safe aggregator list for consistent behavior. (`#702 <https://github.com/libp2p/py-libp2p/issues/702>`__)
25+
26+
27+
Features
28+
~~~~~~~~
29+
30+
- Added support for ``Kademlia DHT`` in py-libp2p. (`#579 <https://github.com/libp2p/py-libp2p/issues/579>`__)
31+
- Limit concurrency in ``push_identify_to_peers`` to prevent resource congestion under high peer counts. (`#621 <https://github.com/libp2p/py-libp2p/issues/621>`__)
32+
- Store public key and peer ID in peerstore during handshake
33+
34+
Modified the InsecureTransport class to accept an optional peerstore parameter and updated the handshake process to store the received public key and peer ID in the peerstore when available.
35+
36+
Added test cases to verify:
37+
1. The peerstore remains unchanged when handshake fails due to peer ID mismatch
38+
2. The handshake correctly adds a public key to a peer ID that already exists in the peerstore but doesn't have a public key yet (`#631 <https://github.com/libp2p/py-libp2p/issues/631>`__)
39+
- Fixed several flow-control and concurrency issues in the ``YamuxStream`` class. Previously, stress-testing revealed that transferring data over ``DEFAULT_WINDOW_SIZE`` would break the stream due to inconsistent window update handling and lock management. The fixes include:
40+
41+
- Removed sending of window updates during writes to maintain correct flow-control.
42+
- Added proper timeout handling when releasing and acquiring locks to prevent concurrency errors.
43+
- Corrected the ``read`` function to properly handle window updates for both ``read_until_EOF`` and ``read_n_bytes``.
44+
- Added event logging at ``send_window_updates`` and ``waiting_for_window_updates`` for better observability. (`#639 <https://github.com/libp2p/py-libp2p/issues/639>`__)
45+
- Added support for ``Multicast DNS`` in py-libp2p (`#649 <https://github.com/libp2p/py-libp2p/issues/649>`__)
46+
- Optimized pubsub publishing to send multiple topics in a single message instead of separate messages per topic. (`#685 <https://github.com/libp2p/py-libp2p/issues/685>`__)
47+
- Optimized pubsub message writing by implementing a write_msg() method that uses pre-allocated buffers and single write operations, improving performance by eliminating separate varint prefix encoding and write operations in FloodSub and GossipSub. (`#687 <https://github.com/libp2p/py-libp2p/issues/687>`__)
48+
- Added peer exchange and backoff logic as part of Gossipsub v1.1 upgrade (`#690 <https://github.com/libp2p/py-libp2p/issues/690>`__)
49+
50+
51+
Internal Changes - for py-libp2p Contributors
52+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53+
54+
- Added sparse connect utility function to pubsub test utilities for creating test networks with configurable connectivity. (`#679 <https://github.com/libp2p/py-libp2p/issues/679>`__)
55+
- Added comprehensive tests for pubsub connection utility functions to verify degree limits are enforced, excess peers are handled correctly, and edge cases (degree=0, negative values, empty lists) are managed gracefully. (`#707 <https://github.com/libp2p/py-libp2p/issues/707>`__)
56+
- Added extra tests for identify push concurrency cap under high peer load (`#708 <https://github.com/libp2p/py-libp2p/issues/708>`__)
57+
58+
59+
Miscellaneous Changes
60+
~~~~~~~~~~~~~~~~~~~~~
61+
62+
- `#678 <https://github.com/libp2p/py-libp2p/issues/678>`__, `#684 <https://github.com/libp2p/py-libp2p/issues/684>`__
63+
64+
665
py-libp2p v0.2.8 (2025-06-10)
766
-----------------------------
867

examples/identify/identify.py

Lines changed: 157 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
from libp2p import (
99
new_host,
1010
)
11-
from libp2p.identity.identify.identify import ID as IDENTIFY_PROTOCOL_ID
12-
from libp2p.identity.identify.pb.identify_pb2 import (
13-
Identify,
11+
from libp2p.identity.identify.identify import (
12+
ID as IDENTIFY_PROTOCOL_ID,
13+
identify_handler_for,
14+
parse_identify_response,
1415
)
1516
from libp2p.peer.peerinfo import (
1617
info_from_p2p_addr,
@@ -50,21 +51,67 @@ def print_identify_response(identify_response):
5051
)
5152

5253

53-
async def run(port: int, destination: str) -> None:
54+
async def run(port: int, destination: str, use_varint_format: bool = True) -> None:
5455
localhost_ip = "0.0.0.0"
5556

5657
if not destination:
5758
# Create first host (listener)
5859
listen_addr = multiaddr.Multiaddr(f"/ip4/{localhost_ip}/tcp/{port}")
5960
host_a = new_host()
6061

62+
# Set up identify handler with specified format
63+
identify_handler = identify_handler_for(
64+
host_a, use_varint_format=use_varint_format
65+
)
66+
host_a.set_stream_handler(IDENTIFY_PROTOCOL_ID, identify_handler)
67+
6168
async with host_a.run(listen_addrs=[listen_addr]):
69+
# Get the actual address and replace 0.0.0.0 with 127.0.0.1 for client
70+
# connections
71+
server_addr = str(host_a.get_addrs()[0])
72+
client_addr = server_addr.replace("/ip4/0.0.0.0/", "/ip4/127.0.0.1/")
73+
74+
format_name = "length-prefixed" if use_varint_format else "raw protobuf"
75+
format_flag = "--raw-format" if not use_varint_format else ""
6276
print(
63-
"First host listening. Run this from another console:\n\n"
64-
f"identify-demo "
65-
f"-d {host_a.get_addrs()[0]}\n"
77+
f"First host listening (using {format_name} format). "
78+
f"Run this from another console:\n\n"
79+
f"identify-demo {format_flag} -d {client_addr}\n"
6680
)
6781
print("Waiting for incoming identify request...")
82+
83+
# Add a custom handler to show connection events
84+
async def custom_identify_handler(stream):
85+
peer_id = stream.muxed_conn.peer_id
86+
print(f"\n🔗 Received identify request from peer: {peer_id}")
87+
88+
# Show remote address in multiaddr format
89+
try:
90+
from libp2p.identity.identify.identify import (
91+
_remote_address_to_multiaddr,
92+
)
93+
94+
remote_address = stream.get_remote_address()
95+
if remote_address:
96+
observed_multiaddr = _remote_address_to_multiaddr(
97+
remote_address
98+
)
99+
# Add the peer ID to create a complete multiaddr
100+
complete_multiaddr = f"{observed_multiaddr}/p2p/{peer_id}"
101+
print(f" Remote address: {complete_multiaddr}")
102+
else:
103+
print(f" Remote address: {remote_address}")
104+
except Exception:
105+
print(f" Remote address: {stream.get_remote_address()}")
106+
107+
# Call the original handler
108+
await identify_handler(stream)
109+
110+
print(f"✅ Successfully processed identify request from {peer_id}")
111+
112+
# Replace the handler with our custom one
113+
host_a.set_stream_handler(IDENTIFY_PROTOCOL_ID, custom_identify_handler)
114+
68115
await trio.sleep_forever()
69116

70117
else:
@@ -79,28 +126,112 @@ async def run(port: int, destination: str) -> None:
79126
info = info_from_p2p_addr(maddr)
80127
print(f"Second host connecting to peer: {info.peer_id}")
81128

82-
await host_b.connect(info)
129+
try:
130+
await host_b.connect(info)
131+
except Exception as e:
132+
error_msg = str(e)
133+
if "unable to connect" in error_msg or "SwarmException" in error_msg:
134+
print(f"\n❌ Cannot connect to peer: {info.peer_id}")
135+
print(f" Address: {destination}")
136+
print(f" Error: {error_msg}")
137+
print(
138+
"\n💡 Make sure the peer is running and the address is correct."
139+
)
140+
return
141+
else:
142+
# Re-raise other exceptions
143+
raise
144+
83145
stream = await host_b.new_stream(info.peer_id, (IDENTIFY_PROTOCOL_ID,))
84146

85147
try:
86148
print("Starting identify protocol...")
87-
response = await stream.read()
149+
150+
# Read the response properly based on the format
151+
if use_varint_format:
152+
# For length-prefixed format, read varint length first
153+
from libp2p.utils.varint import decode_varint_from_bytes
154+
155+
# Read varint length prefix
156+
length_bytes = b""
157+
while True:
158+
b = await stream.read(1)
159+
if not b:
160+
raise Exception("Stream closed while reading varint length")
161+
length_bytes += b
162+
if b[0] & 0x80 == 0:
163+
break
164+
165+
msg_length = decode_varint_from_bytes(length_bytes)
166+
print(f"Expected message length: {msg_length} bytes")
167+
168+
# Read the protobuf message
169+
response = await stream.read(msg_length)
170+
if len(response) != msg_length:
171+
raise Exception(
172+
f"Incomplete message: expected {msg_length} bytes, "
173+
f"got {len(response)}"
174+
)
175+
176+
# Combine length prefix and message
177+
full_response = length_bytes + response
178+
else:
179+
# For raw format, read all available data
180+
response = await stream.read(8192)
181+
full_response = response
182+
88183
await stream.close()
89-
identify_msg = Identify()
90-
identify_msg.ParseFromString(response)
184+
185+
# Parse the response using the robust protocol-level function
186+
# This handles both old and new formats automatically
187+
identify_msg = parse_identify_response(full_response)
91188
print_identify_response(identify_msg)
189+
92190
except Exception as e:
93-
print(f"Identify protocol error: {e}")
191+
error_msg = str(e)
192+
print(f"Identify protocol error: {error_msg}")
193+
194+
# Check for specific format mismatch errors
195+
if "Error parsing message" in error_msg or "DecodeError" in error_msg:
196+
print("\n" + "=" * 60)
197+
print("FORMAT MISMATCH DETECTED!")
198+
print("=" * 60)
199+
if use_varint_format:
200+
print(
201+
"You are using length-prefixed format (default) but the "
202+
"listener"
203+
)
204+
print("is using raw protobuf format.")
205+
print(
206+
"\nTo fix this, run the dialer with the --raw-format flag:"
207+
)
208+
print(f"identify-demo --raw-format -d {destination}")
209+
else:
210+
print("You are using raw protobuf format but the listener")
211+
print("is using length-prefixed format (default).")
212+
print(
213+
"\nTo fix this, run the dialer without the --raw-format "
214+
"flag:"
215+
)
216+
print(f"identify-demo -d {destination}")
217+
print("=" * 60)
218+
else:
219+
import traceback
220+
221+
traceback.print_exc()
94222

95223
return
96224

97225

98226
def main() -> None:
99227
description = """
100228
This program demonstrates the libp2p identify protocol.
101-
First run identify-demo -p <PORT>' to start a listener.
229+
First run 'identify-demo -p <PORT> [--raw-format]' to start a listener.
102230
Then run 'identify-demo <ANOTHER_PORT> -d <DESTINATION>'
103231
where <DESTINATION> is the multiaddress shown by the listener.
232+
233+
Use --raw-format to send raw protobuf messages (old format) instead of
234+
length-prefixed protobuf messages (new format, default).
104235
"""
105236

106237
example_maddr = (
@@ -115,10 +246,22 @@ def main() -> None:
115246
type=str,
116247
help=f"destination multiaddr string, e.g. {example_maddr}",
117248
)
249+
parser.add_argument(
250+
"--raw-format",
251+
action="store_true",
252+
help=(
253+
"use raw protobuf format (old format) instead of "
254+
"length-prefixed (new format)"
255+
),
256+
)
118257
args = parser.parse_args()
119258

259+
# Determine format: raw format if --raw-format is specified, otherwise
260+
# length-prefixed
261+
use_varint_format = not args.raw_format
262+
120263
try:
121-
trio.run(run, *(args.port, args.destination))
264+
trio.run(run, *(args.port, args.destination, use_varint_format))
122265
except KeyboardInterrupt:
123266
pass
124267

0 commit comments

Comments
 (0)