diff --git a/libp2p/transport/websocket/listener.py b/libp2p/transport/websocket/listener.py index be3cc035..7d01ef6b 100644 --- a/libp2p/transport/websocket/listener.py +++ b/libp2p/transport/websocket/listener.py @@ -38,7 +38,10 @@ class WebsocketListener(IListener): or maddr.value_for_protocol("dns6") or "0.0.0.0" ) - port = int(maddr.value_for_protocol("tcp")) + port_str = maddr.value_for_protocol("tcp") + if port_str is None: + raise ValueError(f"No TCP port found in multiaddr: {maddr}") + port = int(port_str) async def serve( task_status: TaskStatus[Any] = trio.TASK_STATUS_IGNORED, diff --git a/libp2p/transport/websocket/transport.py b/libp2p/transport/websocket/transport.py index 4085b556..1d52c758 100644 --- a/libp2p/transport/websocket/transport.py +++ b/libp2p/transport/websocket/transport.py @@ -16,24 +16,38 @@ class WebsocketTransport(ITransport): """ async def dial(self, maddr: Multiaddr) -> RawConnection: - text = str(maddr) - if text.endswith("/wss"): + # Handle addresses with /p2p/ PeerID suffix by truncating them at /ws + addr_text = str(maddr) + try: + ws_part_index = addr_text.index("/ws") + # Create a new Multiaddr containing only the transport part + transport_maddr = Multiaddr(addr_text[: ws_part_index + 3]) + except ValueError: + raise ValueError( + f"WebsocketTransport requires a /ws protocol, not found in {maddr}" + ) from None + + # Check for /wss, which is not supported yet + if str(transport_maddr).endswith("/wss"): raise NotImplementedError("/wss (TLS) not yet supported") - if not text.endswith("/ws"): - raise ValueError(f"WebsocketTransport only supports /ws, got {maddr}") host = ( - maddr.value_for_protocol("ip4") - or maddr.value_for_protocol("ip6") - or maddr.value_for_protocol("dns") - or maddr.value_for_protocol("dns4") - or maddr.value_for_protocol("dns6") + transport_maddr.value_for_protocol("ip4") + or transport_maddr.value_for_protocol("ip6") + or transport_maddr.value_for_protocol("dns") + or transport_maddr.value_for_protocol("dns4") + or transport_maddr.value_for_protocol("dns6") ) if host is None: - raise ValueError(f"No host protocol found in {maddr}") + raise ValueError(f"No host protocol found in {transport_maddr}") - port = int(maddr.value_for_protocol("tcp")) - uri = f"ws://{host}:{port}" + port_str = transport_maddr.value_for_protocol("tcp") + if port_str is None: + raise ValueError(f"No TCP port found in multiaddr: {transport_maddr}") + port = int(port_str) + + host_str = f"[{host}]" if ":" in host else host + uri = f"ws://{host_str}:{port}" try: async with open_websocket_url(uri, ssl_context=None) as ws: diff --git a/tests/interop/js_libp2p/js_node/src/package.json b/tests/interop/js_libp2p/js_node/src/package.json index 1a7a2547..e029c434 100644 --- a/tests/interop/js_libp2p/js_node/src/package.json +++ b/tests/interop/js_libp2p/js_node/src/package.json @@ -12,6 +12,8 @@ "dependencies": { "@libp2p/ping": "^2.0.36", "@libp2p/websockets": "^9.2.18", + "@chainsafe/libp2p-yamux": "^5.0.1", + "@libp2p/plaintext": "^2.0.7", "libp2p": "^2.9.0", "multiaddr": "^10.0.1" } diff --git a/tests/interop/js_libp2p/js_node/src/ws_ping_node.mjs b/tests/interop/js_libp2p/js_node/src/ws_ping_node.mjs index 18988b43..bff7b514 100644 --- a/tests/interop/js_libp2p/js_node/src/ws_ping_node.mjs +++ b/tests/interop/js_libp2p/js_node/src/ws_ping_node.mjs @@ -1,20 +1,20 @@ import { createLibp2p } from 'libp2p' import { webSockets } from '@libp2p/websockets' import { ping } from '@libp2p/ping' -import { plaintext } from '@libp2p/insecure' -import { mplex } from '@libp2p/mplex' +import { plaintext } from '@libp2p/plaintext' +import { yamux } from '@chainsafe/libp2p-yamux' async function main() { const node = await createLibp2p({ transports: [ webSockets() ], connectionEncryption: [ plaintext() ], - streamMuxers: [ mplex() ], + streamMuxers: [ yamux() ], services: { // installs /ipfs/ping/1.0.0 handler ping: ping() }, addresses: { - listen: ['/ip4/127.0.0.1/tcp/0/ws'] + listen: ['/ip4/0.0.0.0/tcp/0/ws'] } }) diff --git a/tests/interop/test_js_ws_ping.py b/tests/interop/test_js_ws_ping.py index 31beb3f6..b2cf248d 100644 --- a/tests/interop/test_js_ws_ping.py +++ b/tests/interop/test_js_ws_ping.py @@ -10,12 +10,13 @@ from trio.lowlevel import open_process from libp2p.crypto.secp256k1 import create_new_key_pair from libp2p.custom_types import TProtocol from libp2p.host.basic_host import BasicHost +from libp2p.network.exceptions import SwarmException from libp2p.network.swarm import Swarm from libp2p.peer.id import ID from libp2p.peer.peerinfo import PeerInfo from libp2p.peer.peerstore import PeerStore from libp2p.security.insecure.transport import InsecureTransport -from libp2p.stream_muxer.mplex.mplex import MPLEX_PROTOCOL_ID, Mplex +from libp2p.stream_muxer.yamux.yamux import Yamux from libp2p.transport.upgrader import TransportUpgrader from libp2p.transport.websocket.transport import WebsocketTransport @@ -24,10 +25,20 @@ PLAINTEXT_PROTOCOL_ID = "/plaintext/1.0.0" @pytest.mark.trio async def test_ping_with_js_node(): - # Path to the JS node script js_node_dir = os.path.join(os.path.dirname(__file__), "js_libp2p", "js_node", "src") script_name = "./ws_ping_node.mjs" + try: + subprocess.run( + ["npm", "install"], + cwd=js_node_dir, + check=True, + capture_output=True, + text=True, + ) + except (subprocess.CalledProcessError, FileNotFoundError) as e: + pytest.fail(f"Failed to run 'npm install': {e}") + # Launch the JS libp2p node (long-running) proc = await open_process( ["node", script_name], @@ -35,22 +46,25 @@ async def test_ping_with_js_node(): stderr=subprocess.PIPE, cwd=js_node_dir, ) + assert proc.stdout is not None, "stdout pipe missing" + assert proc.stderr is not None, "stderr pipe missing" + stdout = proc.stdout + stderr = proc.stderr + try: # Read first two lines (PeerID and multiaddr) buffer = b"" - with trio.fail_after(10): + with trio.fail_after(30): while buffer.count(b"\n") < 2: - chunk = await proc.stdout.receive_some(1024) # type: ignore + chunk = await stdout.receive_some(1024) if not chunk: break buffer += chunk - # Split and filter out any empty lines lines = [line for line in buffer.decode().splitlines() if line.strip()] if len(lines) < 2: - stderr_output = "" - if proc.stderr is not None: - stderr_output = (await proc.stderr.receive_some(2048)).decode() + stderr_output = await stderr.receive_some(2048) + stderr_output = stderr_output.decode() pytest.fail( "JS node did not produce expected PeerID and multiaddr.\n" f"Stdout: {buffer.decode()!r}\n" @@ -70,7 +84,7 @@ async def test_ping_with_js_node(): secure_transports_by_protocol={ TProtocol(PLAINTEXT_PROTOCOL_ID): InsecureTransport(key_pair) }, - muxer_transports_by_protocol={TProtocol(MPLEX_PROTOCOL_ID): Mplex}, + muxer_transports_by_protocol={TProtocol("/yamux/1.0.0"): Yamux}, ) transport = WebsocketTransport() swarm = Swarm(py_peer_id, peer_store, upgrader, transport) @@ -78,9 +92,19 @@ async def test_ping_with_js_node(): # Connect to JS node peer_info = PeerInfo(peer_id, [maddr]) - await host.connect(peer_info) + + await trio.sleep(1) + + try: + await host.connect(peer_info) + except SwarmException as e: + underlying_error = e.__cause__ + pytest.fail( + "Connection failed with SwarmException.\n" + f"THE REAL ERROR IS: {underlying_error!r}\n" + ) + assert host.get_network().connections.get(peer_id) is not None - await trio.sleep(0.1) # Ping protocol stream = await host.new_stream(peer_id, [TProtocol("/ipfs/ping/1.0.0")]) @@ -88,7 +112,6 @@ async def test_ping_with_js_node(): data = await stream.read(4) assert data == b"pong" - # Cleanup await host.close() finally: proc.send_signal(signal.SIGTERM) diff --git a/tests/transport/test_websocket.py b/tests/transport/test_websocket.py index 1270c358..710eeab0 100644 --- a/tests/transport/test_websocket.py +++ b/tests/transport/test_websocket.py @@ -12,7 +12,7 @@ from libp2p.peer.id import ID from libp2p.peer.peerinfo import PeerInfo from libp2p.peer.peerstore import PeerStore from libp2p.security.insecure.transport import InsecureTransport -from libp2p.stream_muxer.mplex.mplex import MPLEX_PROTOCOL_ID, Mplex +from libp2p.stream_muxer.yamux.yamux import Yamux from libp2p.transport.upgrader import TransportUpgrader from libp2p.transport.websocket.transport import WebsocketTransport @@ -33,7 +33,7 @@ async def make_host( secure_transports_by_protocol={ TProtocol(PLAINTEXT_PROTOCOL_ID): InsecureTransport(key_pair) }, - muxer_transports_by_protocol={TProtocol(MPLEX_PROTOCOL_ID): Mplex}, + muxer_transports_by_protocol={TProtocol("/yamux/1.0.0"): Yamux}, ) # Transport + Swarm + Host