Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9336870
feat: multi-transport support (TCP + WebSocket + QUIC simultaneously)
sumanjeet0012 Jun 8, 2026
f2dcc41
feat: Add cmux-based multi-transport host port sharing
sumanjeet0012 Jun 8, 2026
2dede30
renamed conn manager to port demux
sumanjeet0012 Jun 9, 2026
5e4b3c3
removed extra files
sumanjeet0012 Jun 10, 2026
735394a
removed extra files
sumanjeet0012 Jun 10, 2026
13b3b87
refactored quic extra changes
sumanjeet0012 Jun 10, 2026
2f76060
Merge branch 'libp2p:main' into feat/multi_transport_support
sumanjeet0012 Jun 21, 2026
dab3f16
Fix multi-transport routing and registry bugs (BUG-4 to BUG-15)
sumanjeet0012 Jun 24, 2026
3390525
fix(swarm): propagate port_demux listen failures and handle OpenConne…
sumanjeet0012 Jun 24, 2026
6b2b67a
fix(cmux): replace spawn_system_task with nursery tasks
sumanjeet0012 Jun 24, 2026
24cd6b3
test: add multi-port demux and simultaneous transport tests
sumanjeet0012 Jun 28, 2026
cdcea58
Merge branch 'main' of https://github.com/libp2p/py-libp2p into feat/…
sumanjeet0012 Jun 28, 2026
3eaa5ef
fix(lint): properly resolve type checking issues without type: ignore
sumanjeet0012 Jun 28, 2026
763cc53
fix(lint): resolve pyrefly typing errors from CI
sumanjeet0012 Jun 28, 2026
3a1c6b1
fix(lint): replace redundant cast with isinstance to satisfy mypy and…
sumanjeet0012 Jun 28, 2026
717c9e9
fix(lint): remove type: ignore comments for factory classes and updat…
sumanjeet0012 Jun 29, 2026
eac774e
refactor: update comments for clarity and remove obsolete notes in sw…
sumanjeet0012 Jun 29, 2026
aaa41d7
refactor: simplify transport selection logic in new_swarm and improve…
sumanjeet0012 Jun 29, 2026
92b6ecf
Merge branch 'main' into feat/multi_transport_support
sumanjeet0012 Jun 29, 2026
714a150
added newsfragment and removed extra file
sumanjeet0012 Jun 29, 2026
20c7c3f
refactor: remove type ignore comments in anyio service tests for clarity
sumanjeet0012 Jun 29, 2026
25acee6
refactor: update swarm initialization to support multiple transports
sumanjeet0012 Jun 30, 2026
f1e452c
test: improve address verification in host connection test
sumanjeet0012 Jun 30, 2026
8c73384
docs: add multi_transport package documentation and update examples list
sumanjeet0012 Jun 30, 2026
f62f467
refactor: remove unused TYPE_CHECKING import and related conditional
sumanjeet0012 Jun 30, 2026
a039806
Refactor transport handling and remove deprecated transport registry
sumanjeet0012 Jun 30, 2026
6e1df31
refactor: remove transport examples and related documentation
sumanjeet0012 Jun 30, 2026
36b7d08
Fix minor issues in client, swarm, and cmux
sumanjeet0012 Jul 1, 2026
53e25c8
Fix critical/major issues in Swarm backwards compatibility and basic_…
sumanjeet0012 Jul 1, 2026
8aedcc5
Fix line length errors (E501) generated by make pr
sumanjeet0012 Jul 1, 2026
8ec7110
Auto-format client.py to fix pyrefly pre-commit hook failure
sumanjeet0012 Jul 1, 2026
a10f61a
Fix remaining PR feedback from code review
sumanjeet0012 Jul 1, 2026
48053c0
fix: wire enable_webrtc parameter and add multi-transport identify test
sumanjeet0012 Jul 1, 2026
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
37 changes: 37 additions & 0 deletions docs/examples.multi_transport.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
examples.multi\_transport package
=================================

Migration Guide
---------------
The `libp2p.transport.transport_registry` module has been removed, and `Swarm` no longer accepts the `transport=` keyword argument.

To migrate to the new `TransportManager` architecture:
- Pass a list of transports to `new_swarm` or `new_host` using the `transports=[...]` keyword argument.
- If `transports` is omitted, the swarm will automatically detect and create transports based on the provided `listen_addrs`.

Submodules
----------

examples.multi\_transport.client module
---------------------------------------

.. automodule:: examples.multi_transport.client
:members:
:show-inheritance:
:undoc-members:

examples.multi\_transport.server module
---------------------------------------

.. automodule:: examples.multi_transport.server
:members:
:show-inheritance:
:undoc-members:

Module contents
---------------

.. automodule:: examples.multi_transport
:members:
:show-inheritance:
:undoc-members:
3 changes: 2 additions & 1 deletion docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Examples
examples.websocket
examples.tls
examples.tcp
examples.transport

examples.autotls
examples.perf
examples.path_handling
examples.multi_transport
21 changes: 0 additions & 21 deletions docs/examples.transport.rst

This file was deleted.

2 changes: 1 addition & 1 deletion examples/autotls_browser/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async def start_server(self) -> None:
peer_id=peer_id,
peerstore=peer_store,
upgrader=upgrader,
transport=transport,
transports=[transport],
)
self.host = BasicHost(swarm)

Expand Down
1 change: 1 addition & 0 deletions examples/multi_transport/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Multi-transport example package."""
126 changes: 126 additions & 0 deletions examples/multi_transport/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""
Multi-Transport Echo Client — py-libp2p

Connects to the multi-transport echo server using whichever transport
is encoded in the supplied multiaddress.

Usage:
python client.py -d /ip4/127.0.0.1/tcp/4001/p2p/<PEER_ID>
python client.py -d /ip4/127.0.0.1/tcp/4002/ws/p2p/<PEER_ID>
python client.py -d /ip4/127.0.0.1/udp/4003/quic/p2p/<PEER_ID>
"""

import argparse
import logging
from pathlib import Path
import sys

# Ensure local libp2p is used
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

import multiaddr
import trio

from libp2p import new_host
from libp2p.crypto.secp256k1 import create_new_key_pair
from libp2p.custom_types import TProtocol
from libp2p.peer.peerinfo import info_from_p2p_addr

logging.basicConfig(level=logging.WARNING)
logging.getLogger("libp2p").setLevel(logging.WARNING)

ECHO_PROTOCOL = TProtocol("/echo/1.0.0")


def _detect_transport(maddr_str: str) -> str:
"""Return a human-readable transport name from a multiaddr string."""
if "/quic" in maddr_str:
return "QUIC"
if "/ws" in maddr_str or "/wss" in maddr_str:
return "WebSocket"
return "TCP"


async def run_client(
destination: str, message: bytes = b"hello from py-libp2p!\n"
) -> None:
"""
Connect to *destination* (a full /p2p/… multiaddr), send *message*, and
print the echoed reply.

The client inspects the destination multiaddr to enable the right transport
in the Swarm's TransportManager before dialing.
"""
transport_name = _detect_transport(destination)
print(f"=== Multi-Transport Echo Client ({transport_name}) ===\n")

maddr = multiaddr.Multiaddr(destination)
info = info_from_p2p_addr(maddr)

# Enable the transport that matches the destination address.
# new_host() must know which transports to register at construction time —
# the TransportManager is fixed after the Swarm is built.
enable_quic = transport_name == "QUIC"
enable_websocket = transport_name == "WebSocket"

host = new_host(
key_pair=create_new_key_pair(),
enable_quic=enable_quic,
enable_websocket=enable_websocket,
)

# Client doesn't listen — pass an empty list.
async with host.run(listen_addrs=[]):
print(f"My peer ID : {host.get_id().to_string()}")
print(f"Connecting : {destination}\n")

await host.connect(info)
print(f"Connected ✓ (transport: {transport_name})")

stream = await host.new_stream(info.peer_id, [ECHO_PROTOCOL])
await stream.write(message)
# Read exactly the number of bytes we sent to avoid deadlocks
response = await stream.read(len(message))
await stream.close()

print(f"Sent : {message!r}")
print(f"Got : {response!r}")

if response == message:
print("\n✅ Echo verified — round-trip successful!")
else:
print("\n❌ Echo mismatch!")
await trio.sleep(30)


def main() -> None:
parser = argparse.ArgumentParser(
description="Multi-transport echo client (auto-selects TCP / WebSocket / QUIC)."
)
parser.add_argument(
"-d",
"--destination",
required=True,
type=str,
help=(
"Full multiaddr of the server including /p2p/<PEER_ID>, e.g.: "
"/ip4/127.0.0.1/tcp/4001/p2p/16Uiu2..."
),
)
parser.add_argument(
"-m",
"--message",
type=str,
default="hello from py-libp2p!",
help="Message to echo (default: 'hello from py-libp2p!')",
)
args = parser.parse_args()
try:
trio.run(run_client, args.destination, args.message.encode())
except KeyboardInterrupt:
pass


if __name__ == "__main__":
main()
125 changes: 125 additions & 0 deletions examples/multi_transport/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""
Multi-Transport Echo Server — py-libp2p

Demonstrates listening on TCP, WebSocket, and QUIC simultaneously,
mirroring go-libp2p's multi-transport architecture.

Usage:
# Start server (auto-detects free ports on all transports):
python server.py

# Start server on specific port:
python server.py --port 4001

# Start client (copy one of the multiaddrs printed by the server):
python client.py -d /ip4/127.0.0.1/tcp/4001/p2p/<PEER_ID>
python client.py -d /ip4/127.0.0.1/tcp/4001/ws/p2p/<PEER_ID>
python client.py -d /ip4/127.0.0.1/udp/4001/quic/p2p/<PEER_ID>
"""

import argparse
import logging
from pathlib import Path
import sys

# Ensure local libp2p is used
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

import multiaddr
import trio

from libp2p import new_host
from libp2p.crypto.secp256k1 import create_new_key_pair
from libp2p.custom_types import TProtocol
from libp2p.network.stream.net_stream import INetStream
from libp2p.utils.address_validation import find_free_port

# Configure minimal logging
logging.basicConfig(level=logging.WARNING)
logging.getLogger("libp2p").setLevel(logging.WARNING)

ECHO_PROTOCOL = TProtocol("/echo/1.0.0")


async def _echo_handler(stream: INetStream) -> None:
"""Echo handler: read up to 1024 bytes and write it back."""
try:
peer_id = stream.muxed_conn.peer_id
# Read a chunk rather than wait for EOF, preventing deadlocks
# if the client keeps it open
data = await stream.read(1024)
print(f" [{peer_id!s:.20}...] echoing {len(data)} bytes")
await stream.write(data)
await stream.close()
except Exception as exc:
print(f" Handler error: {exc}")
try:
await stream.reset()
except Exception:
pass


async def run_server(port: int = 0) -> None:
"""
Listen simultaneously on TCP, WebSocket, and QUIC using the EXACT SAME PORT.
This demonstrates py-libp2p's connection multiplexing (cmux) capabilities.
"""
if port == 0:
port = find_free_port()

# Build listen addresses for all three transports on the SAME port
listen_addrs = [
multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}"), # plain TCP
multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}/ws"), # WebSocket
multiaddr.Multiaddr(f"/ip4/0.0.0.0/udp/{port}/quic"), # QUIC
]

host = new_host(key_pair=create_new_key_pair(), listen_addrs=listen_addrs)
host.set_stream_handler(ECHO_PROTOCOL, _echo_handler)

print("=== Multi-Transport CMUX Echo Server ===\n")
print(f"Using shared port: {port}\n")
print("Listening on:")

async with host.run(listen_addrs=listen_addrs):
# Wait until the host has bound and resolved all ports
for _ in range(50):
if host.get_addrs():
break
await trio.sleep(0.05)

peer_id = host.get_id().to_string()
for addr in host.get_addrs():
print(f" {addr}")

print(
"\nConnect using any of the following (replace 0.0.0.0 with your IP):\n\n"
f" TCP: python client.py -d "
f"/ip4/127.0.0.1/tcp/{port}/p2p/{peer_id}\n"
f" WebSocket: python client.py -d "
f"/ip4/127.0.0.1/tcp/{port}/ws/p2p/{peer_id}\n"
f" QUIC: python client.py -d "
f"/ip4/127.0.0.1/udp/{port}/quic/p2p/{peer_id}"
)
print("\nWaiting for connections… (Ctrl-C to stop)\n")

await trio.sleep_forever()


def main() -> None:
parser = argparse.ArgumentParser(
description="CMUX echo server (TCP + WebSocket + QUIC on SAME port)."
)
parser.add_argument(
"--port", type=int, default=0, help="Listen port for all transports (0 = auto)"
)
args = parser.parse_args()
try:
trio.run(run_server, args.port)
except KeyboardInterrupt:
print("\nServer stopped.")


if __name__ == "__main__":
main()
Empty file removed examples/transport/__init__.py
Empty file.
Loading
Loading