import os import signal import subprocess import pytest from multiaddr import Multiaddr import trio 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.security.noise.transport import Transport as NoiseTransport from libp2p.crypto.ed25519 import create_new_key_pair as create_ed25519_key_pair from libp2p.stream_muxer.yamux.yamux import Yamux from libp2p.transport.upgrader import TransportUpgrader from libp2p.transport.websocket.transport import WebsocketTransport PLAINTEXT_PROTOCOL_ID = "/plaintext/2.0.0" @pytest.mark.trio async def test_ping_with_js_node(): 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], stdout=subprocess.PIPE, 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(30): while buffer.count(b"\n") < 2: chunk = await stdout.receive_some(1024) if not chunk: break buffer += chunk lines = [line for line in buffer.decode().splitlines() if line.strip()] if len(lines) < 2: 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" f"Stderr: {stderr_output!r}" ) peer_id_line, addr_line = lines[0], lines[1] peer_id = ID.from_base58(peer_id_line) maddr = Multiaddr(addr_line) # Debug: Print what we're trying to connect to print(f"JS Node Peer ID: {peer_id_line}") print(f"JS Node Address: {addr_line}") print(f"All JS Node lines: {lines}") # Set up Python host key_pair = create_new_key_pair() py_peer_id = ID.from_pubkey(key_pair.public_key) peer_store = PeerStore() peer_store.add_key_pair(py_peer_id, key_pair) upgrader = TransportUpgrader( secure_transports_by_protocol={ TProtocol(PLAINTEXT_PROTOCOL_ID): InsecureTransport(key_pair) }, muxer_transports_by_protocol={TProtocol("/yamux/1.0.0"): Yamux}, ) transport = WebsocketTransport(upgrader) swarm = Swarm(py_peer_id, peer_store, upgrader, transport) host = BasicHost(swarm) # Connect to JS node peer_info = PeerInfo(peer_id, [maddr]) print(f"Python trying to connect to: {peer_info}") # Use the host as a context manager async with host.run(listen_addrs=[]): 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 # Ping protocol stream = await host.new_stream(peer_id, [TProtocol("/ipfs/ping/1.0.0")]) await stream.write(b"ping") data = await stream.read(4) assert data == b"pong" finally: proc.send_signal(signal.SIGTERM) await trio.sleep(0)