From 0067b861280886f0001e1d6f9811adb6e6d5a87b Mon Sep 17 00:00:00 2001 From: paschal533 Date: Mon, 9 Jun 2025 00:11:19 +0100 Subject: [PATCH 1/2] Add interoperability test for py-libp2p and js-libp2p with enhanced logging --- tests/interop/js_libp2p/README.md | 81 ++++ tests/interop/js_libp2p/js_node/.gitIgnore | 4 + .../js_node/.github/pull_request_template.md | 17 + .../js_node/.github/workflows/sync.yml | 19 + tests/interop/js_libp2p/js_node/README.md | 53 +++ tests/interop/js_libp2p/js_node/package.json | 39 ++ tests/interop/js_libp2p/js_node/src/ping.js | 204 +++++++++ .../js_libp2p/js_node/src/ping_client.js | 241 +++++++++++ .../js_libp2p/js_node/src/ping_server.js | 167 ++++++++ tests/interop/js_libp2p/py_node/ping.py | 398 ++++++++++++++++++ tests/interop/js_libp2p/scripts/run_test.ps1 | 194 +++++++++ tests/interop/js_libp2p/scripts/run_test.sh | 215 ++++++++++ tests/interop/js_libp2p/test_js_basic.py | 5 - 13 files changed, 1632 insertions(+), 5 deletions(-) create mode 100644 tests/interop/js_libp2p/README.md create mode 100644 tests/interop/js_libp2p/js_node/.gitIgnore create mode 100644 tests/interop/js_libp2p/js_node/.github/pull_request_template.md create mode 100644 tests/interop/js_libp2p/js_node/.github/workflows/sync.yml create mode 100644 tests/interop/js_libp2p/js_node/README.md create mode 100644 tests/interop/js_libp2p/js_node/package.json create mode 100644 tests/interop/js_libp2p/js_node/src/ping.js create mode 100644 tests/interop/js_libp2p/js_node/src/ping_client.js create mode 100644 tests/interop/js_libp2p/js_node/src/ping_server.js create mode 100644 tests/interop/js_libp2p/py_node/ping.py create mode 100644 tests/interop/js_libp2p/scripts/run_test.ps1 create mode 100644 tests/interop/js_libp2p/scripts/run_test.sh delete mode 100644 tests/interop/js_libp2p/test_js_basic.py diff --git a/tests/interop/js_libp2p/README.md b/tests/interop/js_libp2p/README.md new file mode 100644 index 000000000..4c4d40b16 --- /dev/null +++ b/tests/interop/js_libp2p/README.md @@ -0,0 +1,81 @@ +# py-libp2p and js-libp2p Interoperability Tests + +This repository contains interoperability tests for py-libp2p and js-libp2p using the /ipfs/ping/1.0.0 protocol. The goal is to verify compatibility in stream multiplexing, protocol negotiation, ping handling, transport layer, and multiaddr parsing. + +## Directory Structure + +- js_node/ping.js: JavaScript implementation of a ping server and client using libp2p. +- py_node/ping.py: Python implementation of a ping server and client using py-libp2p. +- scripts/run_test.sh: Shell script to automate running the server and client for testing. +- README.md: This file. + +## Prerequisites + +- Python 3.8+ with `py-libp2p` and dependencies (`pip install libp2p trio cryptography multiaddr`). +- Node.js 16+ with `libp2p` dependencies (`npm install @libp2p/core @libp2p/tcp @chainsafe/libp2p-noise @chainsafe/libp2p-yamux @libp2p/ping @libp2p/identify @multiformats/multiaddr`). +- Bash shell for running `run_test.sh`. + +## Running Tests + +1. Change directory: + +``` +cd tests/interop/js_libp2p +``` + +2. Install dependencies: + +``` +For JavaScript: cd js_node && npm install && cd ... +``` + +3. Run the automated test: + +For Linux and Mac users: + +``` +chmod +x scripts/run_test.sh +./scripts/run_test.sh +``` + +For Windows users: + +``` +.\scripts\run_test.ps1 +``` + +This starts the Python server on port 8000 and runs the JavaScript client to send 5 pings. + +## Debugging + +- Logs are saved in py_node/py_server.log and js_node/js_client.log. +- Check for: + - Successful connection establishment. + - Protocol negotiation (/ipfs/ping/1.0.0). + - 32-byte payload echo in server logs. + - RTT and payload hex in client logs. + +## Test Plan + +### The test verifies: + +- Stream Multiplexer Compatibility: Yamux is used and negotiates correctly. +- Multistream Protocol Negotiation: /ipfs/ping/1.0.0 is selected via multistream-select. +- Ping Protocol Handler: Handles 32-byte payloads per the libp2p ping spec. +- Transport Layer Support: TCP is used; WebSocket support is optional. +- Multiaddr Parsing: Correctly resolves multiaddr strings. +- Logging: Includes peer ID, RTT, and payload hex for debugging. + +## Current Status + +### Working: + +- TCP transport and Noise encryption are functional. +- Yamux multiplexing is implemented in both nodes. +- Multiaddr parsing works correctly. +- Logging provides detailed debug information. + +## Not Working: + +- Ping protocol handler fails to complete pings (JS client reports "operation aborted"). +- Potential issues with stream handling or protocol negotiation. diff --git a/tests/interop/js_libp2p/js_node/.gitIgnore b/tests/interop/js_libp2p/js_node/.gitIgnore new file mode 100644 index 000000000..59bb2a9a3 --- /dev/null +++ b/tests/interop/js_libp2p/js_node/.gitIgnore @@ -0,0 +1,4 @@ +/node_modules +/package-lock.json +/dist +.log diff --git a/tests/interop/js_libp2p/js_node/.github/pull_request_template.md b/tests/interop/js_libp2p/js_node/.github/pull_request_template.md new file mode 100644 index 000000000..b47baa1ff --- /dev/null +++ b/tests/interop/js_libp2p/js_node/.github/pull_request_template.md @@ -0,0 +1,17 @@ +# โš ๏ธ IMPORTANT โš ๏ธ + +# Please do not create a Pull Request for this repository + +The contents of this repository are automatically synced from the parent [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) so any changes made to the standalone repository will be lost after the next sync. + +Please open a PR against [js-libp2p Examples](https://github.com/libp2p/js-libp2p-examples) instead. + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) +1. Create your Feature Branch (`git checkout -b feature/amazing-example`) +1. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`) +1. Push to the Branch (`git push origin feature/amazing-example`) +1. Open a Pull Request diff --git a/tests/interop/js_libp2p/js_node/.github/workflows/sync.yml b/tests/interop/js_libp2p/js_node/.github/workflows/sync.yml new file mode 100644 index 000000000..78f6c8d1e --- /dev/null +++ b/tests/interop/js_libp2p/js_node/.github/workflows/sync.yml @@ -0,0 +1,19 @@ +name: pull + +on: + workflow_dispatch + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Pull from another repository + uses: ipfs-examples/actions-pull-directory-from-repo@main + with: + source-repo: libp2p/js-libp2p-examples + source-folder-path: examples/${{ github.event.repository.name }} + source-branch: main + target-branch: main + git-username: github-actions + git-email: github-actions@github.com diff --git a/tests/interop/js_libp2p/js_node/README.md b/tests/interop/js_libp2p/js_node/README.md new file mode 100644 index 000000000..419dfc4aa --- /dev/null +++ b/tests/interop/js_libp2p/js_node/README.md @@ -0,0 +1,53 @@ +# @libp2p/example-chat + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-examples.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-examples) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-examples/ci.yml?branch=main&style=flat-square)](https://github.com/libp2p/js-libp2p-examples/actions/workflows/ci.yml?query=branch%3Amain) + +> An example chat app using libp2p + +## Table of contents + +- [Setup](#setup) +- [Running](#running) +- [Need help?](#need-help) +- [License](#license) +- [Contribution](#contribution) + +## Setup + +1. Install example dependencies + ```console + $ npm install + ``` +1. Open 2 terminal windows in the `./src` directory. + +## Running + +1. Run the listener in window 1, `node listener.js` +1. Run the dialer in window 2, `node dialer.js` +1. Wait until the two peers discover each other +1. Type a message in either window and hit *enter* +1. Tell yourself secrets to your hearts content! + +## Need help? + +- Read the [js-libp2p documentation](https://github.com/libp2p/js-libp2p/tree/main/doc) +- Check out the [js-libp2p API docs](https://libp2p.github.io/js-libp2p/) +- Check out the [general libp2p documentation](https://docs.libp2p.io) for tips, how-tos and more +- Read the [libp2p specs](https://github.com/libp2p/specs) +- Ask a question on the [js-libp2p discussion board](https://github.com/libp2p/js-libp2p/discussions) + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/tests/interop/js_libp2p/js_node/package.json b/tests/interop/js_libp2p/js_node/package.json new file mode 100644 index 000000000..e89ebc8f9 --- /dev/null +++ b/tests/interop/js_libp2p/js_node/package.json @@ -0,0 +1,39 @@ +{ + "name": "@libp2p/example-chat", + "version": "0.0.0", + "description": "An example chat app using libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p-example-chat#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p-examples.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p-examples/issues" + }, + "type": "module", + "scripts": { + "test": "test-node-example test/*" + }, + "dependencies": { + "@chainsafe/libp2p-noise": "^16.0.0", + "@chainsafe/libp2p-yamux": "^7.0.0", + "@libp2p/identify": "^3.0.33", + "@libp2p/mdns": "^11.0.1", + "@libp2p/ping": "^2.0.33", + "@libp2p/tcp": "^10.0.0", + "@libp2p/websockets": "^9.0.0", + "@multiformats/multiaddr": "^12.3.1", + "@nodeutils/defaults-deep": "^1.1.0", + "it-length-prefixed": "^10.0.1", + "it-map": "^3.0.3", + "it-pipe": "^3.0.1", + "libp2p": "^2.0.0", + "p-defer": "^4.0.0", + "uint8arrays": "^5.1.0" + }, + "devDependencies": { + "test-ipfs-example": "^1.1.0" + }, + "private": true +} diff --git a/tests/interop/js_libp2p/js_node/src/ping.js b/tests/interop/js_libp2p/js_node/src/ping.js new file mode 100644 index 000000000..c5a658c78 --- /dev/null +++ b/tests/interop/js_libp2p/js_node/src/ping.js @@ -0,0 +1,204 @@ +#!/usr/bin/env node + +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { ping } from '@libp2p/ping' +import { identify } from '@libp2p/identify' +import { multiaddr } from '@multiformats/multiaddr' + +async function createNode() { + return await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [ + tcp() + ], + connectionEncrypters: [ + noise() + ], + streamMuxers: [ + yamux() + ], + services: { + // Use ipfs prefix to match py-libp2p example + ping: ping({ + protocolPrefix: 'ipfs', + maxInboundStreams: 32, + maxOutboundStreams: 64, + timeout: 30000 + }), + identify: identify() + }, + connectionManager: { + minConnections: 0, + maxConnections: 100, + dialTimeout: 30000 + } + }) +} + +async function runServer() { + console.log('๐Ÿš€ Starting js-libp2p ping server...') + + const node = await createNode() + await node.start() + + console.log('โœ… Server started!') + console.log(`๐Ÿ“‹ Peer ID: ${node.peerId.toString()}`) + console.log('๐Ÿ“ Listening addresses:') + + node.getMultiaddrs().forEach(addr => { + console.log(` ${addr.toString()}`) + }) + + // Listen for connections + node.addEventListener('peer:connect', (evt) => { + console.log(`๐Ÿ”— Peer connected: ${evt.detail.toString()}`) + }) + + node.addEventListener('peer:disconnect', (evt) => { + console.log(`โŒ Peer disconnected: ${evt.detail.toString()}`) + }) + + console.log('\n๐ŸŽง Server ready for ping requests...') + console.log('Press Ctrl+C to exit') + + // Graceful shutdown + process.on('SIGINT', async () => { + console.log('\n๐Ÿ›‘ Shutting down...') + await node.stop() + process.exit(0) + }) + + // Keep alive + while (true) { + await new Promise(resolve => setTimeout(resolve, 1000)) + } +} + +async function runClient(targetAddr, count = 5) { + console.log('๐Ÿš€ Starting js-libp2p ping client...') + + const node = await createNode() + await node.start() + + console.log(`๐Ÿ“‹ Our Peer ID: ${node.peerId.toString()}`) + console.log(`๐ŸŽฏ Target: ${targetAddr}`) + + try { + const ma = multiaddr(targetAddr) + const targetPeerId = ma.getPeerId() + + if (!targetPeerId) { + throw new Error('Could not extract peer ID from multiaddr') + } + + console.log(`๐ŸŽฏ Target Peer ID: ${targetPeerId}`) + console.log('๐Ÿ”— Connecting to peer...') + + const connection = await node.dial(ma) + console.log('โœ… Connection established!') + console.log(`๐Ÿ”— Connected to: ${connection.remotePeer.toString()}`) + + // Add a small delay to let the connection fully establish + await new Promise(resolve => setTimeout(resolve, 1000)) + + const rtts = [] + + for (let i = 1; i <= count; i++) { + try { + console.log(`\n๐Ÿ“ Sending ping ${i}/${count}...`); + console.log('[DEBUG] Attempting to open ping stream with protocol: /ipfs/ping/1.0.0'); + const start = Date.now() + + const stream = await connection.newStream(['/ipfs/ping/1.0.0']).catch(err => { + console.error(`[ERROR] Failed to open ping stream: ${err.message}`); + throw err; + }); + console.log('[DEBUG] Ping stream opened successfully'); + + const latency = await Promise.race([ + node.services.ping.ping(connection.remotePeer), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Ping timeout')), 30000) // Increased timeout + ) + ]).catch(err => { + console.error(`[ERROR] Ping ${i} error: ${err.message}`); + throw err; + }); + + const rtt = Date.now() - start; + + rtts.push(latency) + console.log(`โœ… Ping ${i} successful!`) + console.log(` Reported latency: ${latency}ms`) + console.log(` Measured RTT: ${rtt}ms`) + + if (i < count) { + await new Promise(resolve => setTimeout(resolve, 1000)) + } + } catch (error) { + console.error(`โŒ Ping ${i} failed:`, error.message) + // Try to continue with other pings + } + } + + // Stats + if (rtts.length > 0) { + const avg = rtts.reduce((a, b) => a + b, 0) / rtts.length + const min = Math.min(...rtts) + const max = Math.max(...rtts) + + console.log(`\n๐Ÿ“Š Ping Statistics:`) + console.log(` Packets: Sent=${count}, Received=${rtts.length}, Lost=${count - rtts.length}`) + console.log(` Latency: min=${min}ms, avg=${avg.toFixed(2)}ms, max=${max}ms`) + } else { + console.log(`\n๐Ÿ“Š All pings failed (${count} attempts)`) + } + + } catch (error) { + console.error('โŒ Client error:', error.message) + console.error('Stack:', error.stack) + process.exit(1) + } finally { + await node.stop() + console.log('\nโน๏ธ Client stopped') + } +} + +async function main() { + const args = process.argv.slice(2) + + if (args.length === 0) { + console.log('Usage:') + console.log(' node ping.js server # Start ping server') + console.log(' node ping.js client [count] # Ping a peer') + console.log('') + console.log('Examples:') + console.log(' node ping.js server') + console.log(' node ping.js client /ip4/127.0.0.1/tcp/12345/p2p/12D3Ko... 5') + process.exit(1) + } + + const mode = args[0] + + if (mode === 'server') { + await runServer() + } else if (mode === 'client') { + if (args.length < 2) { + console.error('โŒ Client mode requires target multiaddr') + process.exit(1) + } + const targetAddr = args[1] + const count = parseInt(args[2]) || 5 + await runClient(targetAddr, count) + } else { + console.error('โŒ Invalid mode. Use "server" or "client"') + process.exit(1) + } +} + +main().catch(console.error) diff --git a/tests/interop/js_libp2p/js_node/src/ping_client.js b/tests/interop/js_libp2p/js_node/src/ping_client.js new file mode 100644 index 000000000..4708dd4f6 --- /dev/null +++ b/tests/interop/js_libp2p/js_node/src/ping_client.js @@ -0,0 +1,241 @@ +#!/usr/bin/env node + +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { ping } from '@libp2p/ping' +import { identify } from '@libp2p/identify' +import { multiaddr } from '@multiformats/multiaddr' +import fs from 'fs' +import path from 'path' + +// Create logs directory if it doesn't exist +const logsDir = path.join(process.cwd(), '../logs') +if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }) +} + +// Setup logging +const logFile = path.join(logsDir, 'js_ping_client.log') +const logStream = fs.createWriteStream(logFile, { flags: 'w' }) + +function log(message) { + const timestamp = new Date().toISOString() + const logLine = `${timestamp} - ${message}\n` + logStream.write(logLine) + console.log(message) +} + +async function createNode() { + log('๐Ÿ”ง Creating libp2p node...') + + const node = await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] // Random port + }, + transports: [ + tcp() + ], + connectionEncrypters: [ + noise() + ], + streamMuxers: [ + yamux() + ], + services: { + ping: ping({ + protocolPrefix: 'ipfs', // Use ipfs prefix to match py-libp2p + maxInboundStreams: 32, + maxOutboundStreams: 64, + timeout: 30000, + runOnTransientConnection: true + }), + identify: identify() + }, + connectionManager: { + minConnections: 0, + maxConnections: 100, + dialTimeout: 30000, + maxParallelDials: 10 + } + }) + + log('โœ… Node created successfully') + return node +} + +async function runClient(targetAddr, count = 5) { + log('๐Ÿš€ Starting js-libp2p ping client...') + + const node = await createNode() + + // Add connection event listeners + node.addEventListener('peer:connect', (evt) => { + log(`๐Ÿ”— Connected to peer: ${evt.detail.toString()}`) + }) + + node.addEventListener('peer:disconnect', (evt) => { + log(`โŒ Disconnected from peer: ${evt.detail.toString()}`) + }) + + await node.start() + log('โœ… Node started') + + log(`๐Ÿ“‹ Our Peer ID: ${node.peerId.toString()}`) + log(`๐ŸŽฏ Target: ${targetAddr}`) + + try { + const ma = multiaddr(targetAddr) + const targetPeerId = ma.getPeerId() + + if (!targetPeerId) { + throw new Error('Could not extract peer ID from multiaddr') + } + + log(`๐ŸŽฏ Target Peer ID: ${targetPeerId}`) + + // Parse multiaddr components for debugging + const components = ma.toString().split('/') + log(`๐Ÿ“ Target components: ${components.join(' โ†’ ')}`) + + log('๐Ÿ”— Attempting to dial peer...') + const connection = await node.dial(ma) + log('โœ… Connection established!') + log(`๐Ÿ”— Connected to: ${connection.remotePeer.toString()}`) + log(`๐Ÿ”— Connection status: ${connection.status}`) + log(`๐Ÿ”— Connection direction: ${connection.direction}`) + + // List available protocols + if (connection.remoteAddr) { + log(`๐ŸŒ Remote address: ${connection.remoteAddr.toString()}`) + } + + // Wait for connection to stabilize + log('โณ Waiting for connection to stabilize...') + await new Promise(resolve => setTimeout(resolve, 2000)) + + // Attempt ping sequence + log(`\n๐Ÿ“ Starting ping sequence (${count} pings)...`) + const rtts = [] + + for (let i = 1; i <= count; i++) { + try { + log(`\n๐Ÿ“ Sending ping ${i}/${count}...`) + const start = Date.now() + + // Create a more robust ping with better error handling + const pingPromise = node.services.ping.ping(connection.remotePeer) + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Ping timeout (15s)')), 15000) + ) + + const latency = await Promise.race([pingPromise, timeoutPromise]) + const totalRtt = Date.now() - start + + rtts.push(latency) + log(`โœ… Ping ${i} successful!`) + log(` Reported latency: ${latency}ms`) + log(` Total RTT: ${totalRtt}ms`) + + // Wait between pings + if (i < count) { + await new Promise(resolve => setTimeout(resolve, 1000)) + } + } catch (error) { + log(`โŒ Ping ${i} failed: ${error.message}`) + log(` Error type: ${error.constructor.name}`) + if (error.code) { + log(` Error code: ${error.code}`) + } + + // Check if connection is still alive + if (connection.status !== 'open') { + log(`โš ๏ธ Connection status changed to: ${connection.status}`) + break + } + } + } + + // Print statistics + if (rtts.length > 0) { + const avg = rtts.reduce((a, b) => a + b, 0) / rtts.length + const min = Math.min(...rtts) + const max = Math.max(...rtts) + const lossRate = ((count - rtts.length) / count * 100).toFixed(1) + + log(`\n๐Ÿ“Š Ping Statistics:`) + log(` Packets: Sent=${count}, Received=${rtts.length}, Lost=${count - rtts.length}`) + log(` Loss rate: ${lossRate}%`) + log(` Latency: min=${min}ms, avg=${avg.toFixed(2)}ms, max=${max}ms`) + } else { + log(`\n๐Ÿ“Š All pings failed (${count} attempts)`) + } + + // Close connection gracefully + log('\n๐Ÿ”’ Closing connection...') + await connection.close() + + } catch (error) { + log(`โŒ Client error: ${error.message}`) + log(` Error type: ${error.constructor.name}`) + if (error.stack) { + log(` Stack trace: ${error.stack}`) + } + process.exit(1) + } finally { + log('๐Ÿ›‘ Stopping node...') + await node.stop() + log('โน๏ธ Client stopped') + logStream.end() + } +} + +async function main() { + const args = process.argv.slice(2) + + if (args.length === 0) { + console.log('Usage:') + console.log(' node ping-client.js [count]') + console.log('') + console.log('Examples:') + console.log(' node ping-client.js /ip4/127.0.0.1/tcp/8000/p2p/QmExample... 5') + console.log(' node ping-client.js /ip4/127.0.0.1/tcp/8000/p2p/QmExample... 10') + process.exit(1) + } + + const targetAddr = args[0] + const count = parseInt(args[1]) || 5 + + if (count <= 0 || count > 100) { + console.error('โŒ Count must be between 1 and 100') + process.exit(1) + } + + await runClient(targetAddr, count) +} + +// Handle graceful shutdown +process.on('SIGINT', () => { + log('\n๐Ÿ‘‹ Shutting down...') + logStream.end() + process.exit(0) +}) + +process.on('uncaughtException', (error) => { + log(`๐Ÿ’ฅ Uncaught exception: ${error.message}`) + if (error.stack) { + log(`Stack: ${error.stack}`) + } + logStream.end() + process.exit(1) +}) + +main().catch((error) => { + log(`๐Ÿ’ฅ Fatal error: ${error.message}`) + if (error.stack) { + log(`Stack: ${error.stack}`) + } + logStream.end() + process.exit(1) +}) diff --git a/tests/interop/js_libp2p/js_node/src/ping_server.js b/tests/interop/js_libp2p/js_node/src/ping_server.js new file mode 100644 index 000000000..6188cc65d --- /dev/null +++ b/tests/interop/js_libp2p/js_node/src/ping_server.js @@ -0,0 +1,167 @@ +#!/usr/bin/env node + +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { ping } from '@libp2p/ping' +import { identify } from '@libp2p/identify' +import fs from 'fs' +import path from 'path' + +// Create logs directory if it doesn't exist +const logsDir = path.join(process.cwd(), '../logs') +if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }) +} + +// Setup logging +const logFile = path.join(logsDir, 'js_ping_server.log') +const logStream = fs.createWriteStream(logFile, { flags: 'w' }) + +function log(message) { + const timestamp = new Date().toISOString() + const logLine = `${timestamp} - ${message}\n` + logStream.write(logLine) + console.log(message) +} + +async function createNode(port) { + log('๐Ÿ”ง Creating libp2p node...') + + const node = await createLibp2p({ + addresses: { + listen: [`/ip4/0.0.0.0/tcp/${port}`] + }, + transports: [ + tcp() + ], + connectionEncrypters: [ + noise() + ], + streamMuxers: [ + yamux() + ], + services: { + ping: ping({ + protocolPrefix: 'ipfs', // Use ipfs prefix to match py-libp2p + maxInboundStreams: 32, + maxOutboundStreams: 64, + timeout: 30000, + runOnTransientConnection: true + }), + identify: identify() + }, + connectionManager: { + minConnections: 0, + maxConnections: 100, + dialTimeout: 30000, + maxParallelDials: 10 + } + }) + + log('โœ… Node created successfully') + return node +} + +async function runServer(port) { + log('๐Ÿš€ Starting js-libp2p ping server...') + + const node = await createNode(port) + + // Add connection event listeners + node.addEventListener('peer:connect', (evt) => { + log(`๐Ÿ”— New peer connected: ${evt.detail.toString()}`) + }) + + node.addEventListener('peer:disconnect', (evt) => { + log(`โŒ Peer disconnected: ${evt.detail.toString()}`) + }) + + // Add protocol handler for incoming streams + node.addEventListener('peer:identify', (evt) => { + log(`๐Ÿ” Peer identified: ${evt.detail.peerId.toString()}`) + log(` Protocols: ${evt.detail.protocols.join(', ')}`) + log(` Listen addresses: ${evt.detail.listenAddrs.map(addr => addr.toString()).join(', ')}`) + }) + + await node.start() + log('โœ… Node started') + + const peerId = node.peerId.toString() + const listenAddrs = node.getMultiaddrs() + + log(`๐Ÿ“‹ Peer ID: ${peerId}`) + log(`๐ŸŒ Listen addresses:`) + listenAddrs.forEach(addr => { + log(` ${addr.toString()}`) + }) + + // Find the main TCP address for easy copy-paste + const tcpAddr = listenAddrs.find(addr => + addr.toString().includes('/tcp/') && + !addr.toString().includes('/ws') + ) + + if (tcpAddr) { + log(`\n๐Ÿงช Test with py-libp2p:`) + log(` python ping_client.py ${tcpAddr.toString()}`) + log(`\n๐Ÿงช Test with js-libp2p:`) + log(` node ping-client.js ${tcpAddr.toString()}`) + } + + log(`\n๐Ÿ“ Ping service is running with protocol: /ipfs/ping/1.0.0`) + log(`๐Ÿ” Security: Noise encryption`) + log(`๐Ÿš‡ Muxer: Yamux stream multiplexing`) + log(`\nโณ Waiting for connections...`) + log('Press Ctrl+C to exit') + + // Keep the server running + return new Promise((resolve, reject) => { + process.on('SIGINT', () => { + log('\n๐Ÿ›‘ Shutting down server...') + node.stop().then(() => { + log('โน๏ธ Server stopped') + logStream.end() + resolve() + }).catch(reject) + }) + + process.on('uncaughtException', (error) => { + log(`๐Ÿ’ฅ Uncaught exception: ${error.message}`) + if (error.stack) { + log(`Stack: ${error.stack}`) + } + logStream.end() + reject(error) + }) + }) +} + +async function main() { + const args = process.argv.slice(2) + const port = parseInt(args[0]) || 9000 + + if (port <= 0 || port > 65535) { + console.error('โŒ Port must be between 1 and 65535') + process.exit(1) + } + + try { + await runServer(port) + } catch (error) { + console.error(`๐Ÿ’ฅ Fatal error: ${error.message}`) + if (error.stack) { + console.error(`Stack: ${error.stack}`) + } + process.exit(1) + } +} + +main().catch((error) => { + console.error(`๐Ÿ’ฅ Fatal error: ${error.message}`) + if (error.stack) { + console.error(`Stack: ${error.stack}`) + } + process.exit(1) +}) diff --git a/tests/interop/js_libp2p/py_node/ping.py b/tests/interop/js_libp2p/py_node/ping.py new file mode 100644 index 000000000..a13a8acee --- /dev/null +++ b/tests/interop/js_libp2p/py_node/ping.py @@ -0,0 +1,398 @@ +import argparse +import logging + +from cryptography.hazmat.primitives.asymmetric import ( + x25519, +) +import multiaddr +import trio + +from libp2p import ( + generate_new_rsa_identity, + new_host, +) +from libp2p.custom_types import ( + TProtocol, +) +from libp2p.network.stream.net_stream import ( + INetStream, +) +from libp2p.peer.peerinfo import ( + info_from_p2p_addr, +) +from libp2p.security.noise.transport import Transport as NoiseTransport +from libp2p.stream_muxer.yamux.yamux import ( + Yamux, +) +from libp2p.stream_muxer.yamux.yamux import PROTOCOL_ID as YAMUX_PROTOCOL_ID + +# Configure detailed logging +logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", + handlers=[ + logging.StreamHandler(), + logging.FileHandler("ping_debug.log", mode="w", encoding="utf-8"), + ], +) + +PING_PROTOCOL_ID = TProtocol("/ipfs/ping/1.0.0") +PING_LENGTH = 32 +RESP_TIMEOUT = 60 + + +async def handle_ping(stream: INetStream) -> None: + """Handle incoming ping requests from js-libp2p clients""" + peer_id = stream.muxed_conn.peer_id + print(f"[INFO] New ping stream opened by {peer_id}") + logging.info(f"Ping handler called for peer {peer_id}") + + ping_count = 0 + + try: + while True: + try: + print(f"[INFO] Waiting for ping data from {peer_id}...") + logging.debug(f"Stream state: {stream}") + data = await stream.read(PING_LENGTH) + + if not data: + print( + f"[INFO] No data received," + f"connection likely closed by {peer_id}" + ) + logging.debug("No data received, stream closed") + break + + if len(data) == 0: + print(f"[INFO] Empty data received, connection closed by {peer_id}") + logging.debug("Empty data received") + break + + ping_count += 1 + print( + f"[PING {ping_count}] Received ping from {peer_id}:" + f"{len(data)} bytes" + ) + logging.debug(f"Ping data: {data.hex()}") + + await stream.write(data) + print(f"[PING {ping_count}] Echoed ping back to {peer_id}") + + except Exception as e: + print(f"[ERROR] Error in ping loop with {peer_id}: {e}") + logging.exception("Ping loop error") + break + + except Exception as e: + print(f"[ERROR] Error handling ping from {peer_id}: {e}") + logging.exception("Ping handler error") + finally: + try: + print(f"[INFO] Closing ping stream with {peer_id}") + await stream.close() + except Exception as e: + logging.debug(f"Error closing stream: {e}") + + print(f"[INFO] Ping session completed with {peer_id} ({ping_count} pings)") + + +async def send_ping_sequence(stream: INetStream, count: int = 5) -> None: + """Send a sequence of pings compatible with js-libp2p.""" + peer_id = stream.muxed_conn.peer_id + print(f"[INFO] Starting ping sequence to {peer_id} ({count} pings)") + + import os + import time + + rtts = [] + + for i in range(1, count + 1): + try: + payload = os.urandom(PING_LENGTH) + print(f"[PING {i}/{count}] Sending ping to {peer_id}") + logging.debug(f"Sending payload: {payload.hex()}") + start_time = time.time() + + await stream.write(payload) + + with trio.fail_after(RESP_TIMEOUT): + response = await stream.read(PING_LENGTH) + + end_time = time.time() + rtt = (end_time - start_time) * 1000 + + if ( + response + and len(response) >= PING_LENGTH + and response[:PING_LENGTH] == payload + ): + rtts.append(rtt) + print(f"[PING {i}] Successful! RTT: {rtt:.2f}ms") + else: + print(f"[ERROR] Ping {i} failed: response mismatch or incomplete") + if response: + logging.debug(f"Expected: {payload.hex()}") + logging.debug(f"Received: {response.hex()}") + + if i < count: + await trio.sleep(1) + + except trio.TooSlowError: + print(f"[ERROR] Ping {i} timed out after {RESP_TIMEOUT}s") + except Exception as e: + print(f"[ERROR] Ping {i} failed: {e}") + logging.exception(f"Ping {i} error") + + if rtts: + avg_rtt = sum(rtts) / len(rtts) + min_rtt = min(rtts) + max_rtts = max(rtts) + success_count = len(rtts) + loss_rate = ((count - success_count) / count) * 100 + + print( + f" Packets: Sent={count}, Received={success_count}," + f" Lost={count - success_count}" + ) + print(f" Loss rate: {loss_rate:.1f}%") + print( + f" RTT: min={min_rtt:.2f}ms, avg={avg_rtt:.2f}ms," f"max={max_rtts:.2f}ms" + ) + else: + print(f"\n[STATS] All pings failed ({count} attempts)") + + +def create_noise_keypair(): + try: + x25519_private_key = x25519.X25519PrivateKey.generate() + + class NoisePrivateKey: + def __init__(self, key): + self._key = key + + def to_bytes(self): + return self._key.private_bytes_raw() + + def public_key(self): + return NoisePublicKey(self._key.public_key()) + + def get_public_key(self): + return NoisePublicKey(self._key.public_key()) + + class NoisePublicKey: + def __init__(self, key): + self._key = key + + def to_bytes(self): + return self._key.public_bytes_raw() + + return NoisePrivateKey(x25519_private_key) + except Exception as e: + logging.error(f"Failed to create Noise keypair: {e}") + return None + + +async def run_server(port: int) -> None: + """Run ping server that accepts connections from js-libp2p clients.""" + listen_addr = multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}") + + key_pair = generate_new_rsa_identity() + logging.debug("Generated RSA keypair") + + noise_privkey = create_noise_keypair() + logging.debug("Generated Noise keypair") + + noise_transport = NoiseTransport(key_pair, noise_privkey=noise_privkey) + logging.debug(f"Noise transport initialized: {noise_transport}") + sec_opt = {TProtocol("/noise"): noise_transport} + muxer_opt = {TProtocol(YAMUX_PROTOCOL_ID): Yamux} + + logging.info(f"Using muxer: {muxer_opt}") + + host = new_host(key_pair=key_pair, sec_opt=sec_opt, muxer_opt=muxer_opt) + + print("[INFO] Starting py-libp2p ping server...") + + async with host.run(listen_addrs=[listen_addr]): + print(f"[INFO] Registering ping handler for protocol: {PING_PROTOCOL_ID}") + host.set_stream_handler(PING_PROTOCOL_ID, handle_ping) + + alt_protocols = [ + TProtocol("/ping/1.0.0"), + TProtocol("/libp2p/ping/1.0.0"), + ] + + for alt_proto in alt_protocols: + print(f"[INFO] Also registering handler for: {alt_proto}") + host.set_stream_handler(alt_proto, handle_ping) + + print("[INFO] Server started!") + print(f"[INFO] Peer ID: {host.get_id()}") + print(f"[INFO] Listening: /ip4/0.0.0.0/tcp/{port}") + print(f"[INFO] Primary Protocol: {PING_PROTOCOL_ID}") + # print(f"[INFO] Security: Noise encryption") + # print(f"[INFO] Muxer: Yamux stream multiplexing") + + print("\n[INFO] Registered protocols:") + print(f" - {PING_PROTOCOL_ID}") + for proto in alt_protocols: + print(f" - {proto}") + + peer_id = host.get_id() + print("\n[TEST] Test with js-libp2p:") + print(f" node ping.js client /ip4/127.0.0.1/tcp/{port}/p2p/{peer_id}") + + print("\n[TEST] Test with py-libp2p:") + print(f" python ping.py client /ip4/127.0.0.1/tcp/{port}/p2p/{peer_id}") + + print("\n[INFO] Waiting for connections...") + print("Press Ctrl+C to exit") + + await trio.sleep_forever() + + +async def run_client(destination: str, count: int = 5) -> None: + """Run ping client to test connectivity with another peer.""" + listen_addr = multiaddr.Multiaddr("/ip4/0.0.0.0/tcp/0") + + key_pair = generate_new_rsa_identity() + logging.debug("Generated RSA keypair") + + noise_privkey = create_noise_keypair() + logging.debug("Generated Noise keypair") + + noise_transport = NoiseTransport(key_pair, noise_privkey=noise_privkey) + logging.debug(f"Noise transport initialized: {noise_transport}") + sec_opt = {TProtocol("/noise"): noise_transport} + muxer_opt = {TProtocol(YAMUX_PROTOCOL_ID): Yamux} + + logging.info(f"Using muxer: {muxer_opt}") + + host = new_host(key_pair=key_pair, sec_opt=sec_opt, muxer_opt=muxer_opt) + + print("[INFO] Starting py-libp2p ping client...") + + async with host.run(listen_addrs=[listen_addr]): + print(f"[INFO] Our Peer ID: {host.get_id()}") + print(f"[INFO] Target: {destination}") + print("[INFO] Security: Noise encryption") + print("[INFO] Muxer: Yamux stream multiplexing") + + try: + maddr = multiaddr.Multiaddr(destination) + info = info_from_p2p_addr(maddr) + target_peer_id = info.peer_id + + print(f"[INFO] Target Peer ID: {target_peer_id}") + print("[INFO] Connecting to peer...") + + await host.connect(info) + print("[INFO] Connection established!") + + protocols_to_try = [ + PING_PROTOCOL_ID, + TProtocol("/ping/1.0.0"), + TProtocol("/libp2p/ping/1.0.0"), + ] + + stream = None + + for proto in protocols_to_try: + try: + print(f"[INFO] Trying to open stream with protocol: {proto}") + stream = await host.new_stream(target_peer_id, [proto]) + print(f"[INFO] Stream opened with protocol: {proto}") + break + except Exception as e: + print(f"[ERROR] Failed to open stream with {proto}: {e}") + continue + + if not stream: + print("[ERROR] Failed to open stream with any ping protocol") + return 1 + + await send_ping_sequence(stream, count) + + await stream.close() + + except Exception as e: + print(f"[ERROR] Client error: {e}") + import traceback + + traceback.print_exc() + return 1 + + print("\n[INFO] Client stopped") + return 0 + + +def main() -> None: + """Main function with argument parsing.""" + description = """ + py-libp2p ping tool for interoperability testing with js-libp2p. + Uses Noise encryption and Yamux multiplexing for compatibility. + + Server mode: Listens for ping requests from js-libp2p or py-libp2p clients. + Client mode: Sends ping requests to js-libp2p or py-libp2p servers. + """ + + example_maddr = ( + "/ip4/127.0.0.1/tcp/8000/p2p/QmQn4SwGkDZKkUEpBRBvTmheQycxAHJUNmVEnjA2v1qe8Q" + ) + + parser = argparse.ArgumentParser( + description=description, + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=f""" +Examples: + python ping.py server # Start server on port 8000 + python ping.py server --port 9000 # Start server on port 9000 + python ping.py client {example_maddr} + python ping.py client {example_maddr} --count 10 + """, + ) + + subparsers = parser.add_subparsers(dest="mode", help="Operation mode") + + server_parser = subparsers.add_parser("server", help="Run as ping server") + server_parser.add_argument( + "--port", "-p", type=int, default=8000, help="Port to listen on (default: 8000)" + ) + + client_parser = subparsers.add_parser("client", help="Run as ping client") + client_parser.add_argument("destination", help="Target peer multiaddr") + client_parser.add_argument( + "--count", + "-c", + type=int, + default=5, + help="Number of pings to send (default: 5)", + ) + + args = parser.parse_args() + + if not args.mode: + parser.print_help() + return 1 + + try: + if args.mode == "server": + trio.run(run_server, args.port) + elif args.mode == "client": + return trio.run(run_client, args.destination, args.count) + except KeyboardInterrupt: + print("\n[INFO] Goodbye!") + return 0 + except Exception as e: + print(f"[ERROR] Fatal error: {e}") + import traceback + + traceback.print_exc() + return 1 + + return 0 + + +if __name__ == "__main__": + exit(main()) diff --git a/tests/interop/js_libp2p/scripts/run_test.ps1 b/tests/interop/js_libp2p/scripts/run_test.ps1 new file mode 100644 index 000000000..9654fc500 --- /dev/null +++ b/tests/interop/js_libp2p/scripts/run_test.ps1 @@ -0,0 +1,194 @@ +#!/usr/bin/env pwsh + +# run_test.ps1 - libp2p Interoperability Test Runner (PowerShell) +# Tests py-libp2p <-> js-libp2p ping communication + +$ErrorActionPreference = "Stop" + +# Colors for output +$Red = "`e[31m" +$Green = "`e[32m" +$Yellow = "`e[33m" +$Blue = "`e[34m" +$Cyan = "`e[36m" +$Reset = "`e[0m" + +function Write-ColorOutput { + param([string]$Message, [string]$Color = $Reset) + Write-Host "${Color}${Message}${Reset}" +} + +Write-ColorOutput "[CHECK] Checking prerequisites..." $Cyan +if (-not (Get-Command python -ErrorAction SilentlyContinue)) { + Write-ColorOutput "[ERROR] Python not found. Install Python 3.7+" $Red + exit 1 +} +if (-not (Get-Command node -ErrorAction SilentlyContinue)) { + Write-ColorOutput "[ERROR] Node.js not found. Install Node.js 16+" $Red + exit 1 +} + +Write-ColorOutput "[CHECK] Checking port 8000..." $Blue +$portCheck = netstat -a -n -o | findstr :8000 +if ($portCheck) { + Write-ColorOutput "[ERROR] Port 8000 in use. Free the port." $Red + Write-ColorOutput $portCheck $Yellow + exit 1 +} + +Write-ColorOutput "[DEBUG] Cleaning up Python processes..." $Blue +Get-Process -Name "python" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like "*ping.py*" } | Stop-Process -Force -ErrorAction SilentlyContinue + +Write-ColorOutput "[PYTHON] Starting server on port 8000..." $Yellow +Set-Location -Path "py_node" +$pyLogFile = "py_server_8000.log" +$pyErrLogFile = "py_server_8000.log.err" +$pyDebugLogFile = "ping_debug.log" + +if (Test-Path $pyLogFile) { Remove-Item $pyLogFile -Force -ErrorAction SilentlyContinue } +if (Test-Path $pyErrLogFile) { Remove-Item $pyErrLogFile -Force -ErrorAction SilentlyContinue } +if (Test-Path $pyDebugLogFile) { Remove-Item $pyDebugLogFile -Force -ErrorAction SilentlyContinue } + +$pyProcess = Start-Process -FilePath "python" -ArgumentList "-u", "ping.py", "server", "--port", "8000" -NoNewWindow -PassThru -RedirectStandardOutput $pyLogFile -RedirectStandardError $pyErrLogFile +Write-ColorOutput "[DEBUG] Python server PID: $($pyProcess.Id)" $Blue +Write-ColorOutput "[DEBUG] Python logs: $((Get-Location).Path)\$pyLogFile, $((Get-Location).Path)\$pyErrLogFile, $((Get-Location).Path)\$pyDebugLogFile" $Blue + +$timeoutSeconds = 20 +$startTime = Get-Date +$serverStarted = $false + +while (((Get-Date) - $startTime).TotalSeconds -lt $timeoutSeconds -and -not $serverStarted) { + if (Test-Path $pyLogFile) { + $content = Get-Content $pyLogFile -Raw -ErrorAction SilentlyContinue + if ($content -match "Server started|Listening") { + $serverStarted = $true + Write-ColorOutput "[OK] Python server started" $Green + } + } + if (Test-Path $pyErrLogFile) { + $errContent = Get-Content $pyErrLogFile -Raw -ErrorAction SilentlyContinue + if ($errContent) { + Write-ColorOutput "[DEBUG] Error log: $errContent" $Yellow + } + } + Start-Sleep -Milliseconds 500 +} + +if (-not $serverStarted) { + Write-ColorOutput "[ERROR] Python server failed to start" $Red + Write-ColorOutput "[DEBUG] Logs:" $Yellow + if (Test-Path $pyLogFile) { Get-Content $pyLogFile | Write-ColorOutput -Color $Yellow } + if (Test-Path $pyErrLogFile) { Get-Content $pyErrLogFile | Write-ColorOutput -Color $Yellow } + if (Test-Path $pyDebugLogFile) { Get-Content $pyDebugLogFile | Write-ColorOutput -Color $Yellow } + Write-ColorOutput "[DEBUG] Trying foreground run..." $Yellow + python -u ping.py server --port 8000 + exit 1 +} + +# Extract Peer ID +$peerInfo = $null +if (Test-Path $pyLogFile) { + $content = Get-Content $pyLogFile -Raw + $peerIdPattern = "Peer ID:\s*([A-Za-z0-9]+)" + $peerIdMatch = [regex]::Match($content, $peerIdPattern) + if ($peerIdMatch.Success) { + $peerId = $peerIdMatch.Groups[1].Value + $peerInfo = @{ + PeerId = $peerId + MultiAddr = "/ip4/127.0.0.1/tcp/8000/p2p/$peerId" + } + Write-ColorOutput "[OK] Peer ID: $peerId" $Cyan + Write-ColorOutput "[OK] MultiAddr: $($peerInfo.MultiAddr)" $Cyan + } +} + +if (-not $peerInfo) { + Write-ColorOutput "[ERROR] Could not extract Peer ID" $Red + if (Test-Path $pyLogFile) { Get-Content $pyLogFile | Write-ColorOutput -Color $Yellow } + if (Test-Path $pyErrLogFile) { Get-Content $pyErrLogFile | Write-ColorOutput -Color $Yellow } + if (Test-Path $pyDebugLogFile) { Get-Content $pyDebugLogFile | Write-ColorOutput -Color $Yellow } + Stop-Process -Id $pyProcess.Id -Force -ErrorAction SilentlyContinue + exit 1 +} + +# Start JavaScript client +Write-ColorOutput "[JAVASCRIPT] Starting client..." $Yellow +Set-Location -Path "../js_node" +$jsLogFile = "test_js_client_to_py_server.log" +$jsErrLogFile = "test_js_client_to_py_server.log.err" + +if (Test-Path $jsLogFile) { Remove-Item $jsLogFile -Force -ErrorAction SilentlyContinue } +if (Test-Path $jsErrLogFile) { Remove-Item $jsErrLogFile -Force -ErrorAction SilentlyContinue } + +$jsProcess = Start-Process -FilePath "node" -ArgumentList "src/ping.js", "client", $peerInfo.MultiAddr, "3" -NoNewWindow -PassThru -RedirectStandardOutput $jsLogFile -RedirectStandardError $jsErrLogFile +Write-ColorOutput "[DEBUG] JavaScript client PID: $($jsProcess.Id)" $Blue +Write-ColorOutput "[DEBUG] Client logs: $((Get-Location).Path)\$jsLogFile, $((Get-Location).Path)\$jsErrLogFile" $Blue + +# Wait for client to complete +$clientTimeout = 10 +$clientStart = Get-Date +while (-not $jsProcess.HasExited -and (((Get-Date) - $clientStart).TotalSeconds -lt $clientTimeout)) { + Start-Sleep -Seconds 1 +} + +if (-not $jsProcess.HasExited) { + Write-ColorOutput "[DEBUG] JavaScript client did not exit, terminating..." $Yellow + Stop-Process -Id $jsProcess.Id -Force -ErrorAction SilentlyContinue +} + +Write-ColorOutput "[CHECK] Results..." $Cyan +$success = $false +if (Test-Path $jsLogFile) { + $jsLogContent = Get-Content $jsLogFile -Raw -ErrorAction SilentlyContinue + if ($jsLogContent -match "successful|Ping.*successful") { + $success = $true + Write-ColorOutput "[SUCCESS] Ping test passed" $Green + } else { + Write-ColorOutput "[FAILED] No successful pings" $Red + Write-ColorOutput "[DEBUG] Client log path: $((Get-Location).Path)\$jsLogFile" $Yellow + Write-ColorOutput "Client log:" $Yellow + Write-ColorOutput $jsLogContent $Yellow + if (Test-Path $jsErrLogFile) { + Write-ColorOutput "[DEBUG] Client error log path: $((Get-Location).Path)\$jsErrLogFile" $Yellow + Write-ColorOutput "Client error log:" $Yellow + Get-Content $jsErrLogFile | Write-ColorOutput -Color $Yellow + } + Write-ColorOutput "[DEBUG] Python server log path: $((Get-Location).Path)\..\py_node\$pyLogFile" $Yellow + Write-ColorOutput "Python server log:" $Yellow + if (Test-Path "../py_node/$pyLogFile") { + $pyLogContent = Get-Content "../py_node/$pyLogFile" -Raw -ErrorAction SilentlyContinue + if ($pyLogContent) { Write-ColorOutput $pyLogContent $Yellow } else { Write-ColorOutput "Empty or inaccessible" $Yellow } + } else { + Write-ColorOutput "File not found" $Yellow + } + Write-ColorOutput "[DEBUG] Python server error log path: $((Get-Location).Path)\..\py_node\$pyErrLogFile" $Yellow + Write-ColorOutput "Python server error log:" $Yellow + if (Test-Path "../py_node/$pyErrLogFile") { + $pyErrLogContent = Get-Content "../py_node/$pyErrLogFile" -Raw -ErrorAction SilentlyContinue + if ($pyErrLogContent) { Write-ColorOutput $pyErrLogContent $Yellow } else { Write-ColorOutput "Empty or inaccessible" $Yellow } + } else { + Write-ColorOutput "File not found" $Yellow + } + Write-ColorOutput "[DEBUG] Python debug log path: $((Get-Location).Path)\..\py_node\$pyDebugLogFile" $Yellow + Write-ColorOutput "Python debug log:" $Yellow + if (Test-Path "../py_node/$pyDebugLogFile") { + $pyDebugLogContent = Get-Content "../py_node/$pyDebugLogFile" -Raw -ErrorAction SilentlyContinue + if ($pyDebugLogContent) { Write-ColorOutput $pyDebugLogContent $Yellow } else { Write-ColorOutput "Empty or inaccessible" $Yellow } + } else { + Write-ColorOutput "File not found" $Yellow + } + } +} + +Write-ColorOutput "[CLEANUP] Stopping processes..." $Yellow +Stop-Process -Id $pyProcess.Id -Force -ErrorAction SilentlyContinue +Stop-Process -Id $jsProcess.Id -Force -ErrorAction SilentlyContinue +Set-Location -Path "../" + +if ($success) { + Write-ColorOutput "[SUCCESS] Test completed" $Green + exit 0 +} else { + Write-ColorOutput "[FAILED] Test failed" $Red + exit 1 +} diff --git a/tests/interop/js_libp2p/scripts/run_test.sh b/tests/interop/js_libp2p/scripts/run_test.sh new file mode 100644 index 000000000..cbf9e6270 --- /dev/null +++ b/tests/interop/js_libp2p/scripts/run_test.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash + +# run_test.sh - libp2p Interoperability Test Runner (Bash) +# Tests py-libp2p <-> js-libp2p ping communication + +set -e + +# Colors for output +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +CYAN='\033[36m' +RESET='\033[0m' + +write_color_output() { + local message="$1" + local color="${2:-$RESET}" + echo -e "${color}${message}${RESET}" +} + +write_color_output "[CHECK] Checking prerequisites..." "$CYAN" +if ! command -v python3 &> /dev/null && ! command -v python &> /dev/null; then + write_color_output "[ERROR] Python not found. Install Python 3.7+" "$RED" + exit 1 +fi + +# Use python3 if available, otherwise python +PYTHON_CMD="python3" +if ! command -v python3 &> /dev/null; then + PYTHON_CMD="python" +fi + +if ! command -v node &> /dev/null; then + write_color_output "[ERROR] Node.js not found. Install Node.js 16+" "$RED" + exit 1 +fi + +write_color_output "[CHECK] Checking port 8000..." "$BLUE" +if netstat -tuln 2>/dev/null | grep -q ":8000 " || ss -tuln 2>/dev/null | grep -q ":8000 "; then + write_color_output "[ERROR] Port 8000 in use. Free the port." "$RED" + if command -v netstat &> /dev/null; then + netstat -tuln | grep ":8000 " | write_color_output "$(cat)" "$YELLOW" + elif command -v ss &> /dev/null; then + ss -tuln | grep ":8000 " | write_color_output "$(cat)" "$YELLOW" + fi + exit 1 +fi + +write_color_output "[DEBUG] Cleaning up Python processes..." "$BLUE" +pkill -f "ping.py" 2>/dev/null || true + +write_color_output "[PYTHON] Starting server on port 8000..." "$YELLOW" +cd py_node + +PY_LOG_FILE="py_server_8000.log" +PY_ERR_LOG_FILE="py_server_8000.log.err" +PY_DEBUG_LOG_FILE="ping_debug.log" + +rm -f "$PY_LOG_FILE" "$PY_ERR_LOG_FILE" "$PY_DEBUG_LOG_FILE" + +$PYTHON_CMD -u ping.py server --port 8000 > "$PY_LOG_FILE" 2> "$PY_ERR_LOG_FILE" & +PY_PROCESS_PID=$! + +write_color_output "[DEBUG] Python server PID: $PY_PROCESS_PID" "$BLUE" +write_color_output "[DEBUG] Python logs: $(pwd)/$PY_LOG_FILE, $(pwd)/$PY_ERR_LOG_FILE, $(pwd)/$PY_DEBUG_LOG_FILE" "$BLUE" + +TIMEOUT_SECONDS=20 +START_TIME=$(date +%s) +SERVER_STARTED=false + +while [ $(($(date +%s) - START_TIME)) -lt $TIMEOUT_SECONDS ] && [ "$SERVER_STARTED" = false ]; do + if [ -f "$PY_LOG_FILE" ]; then + if grep -q "Server started\|Listening" "$PY_LOG_FILE" 2>/dev/null; then + SERVER_STARTED=true + write_color_output "[OK] Python server started" "$GREEN" + fi + fi + if [ -f "$PY_ERR_LOG_FILE" ] && [ -s "$PY_ERR_LOG_FILE" ]; then + ERR_CONTENT=$(cat "$PY_ERR_LOG_FILE" 2>/dev/null || true) + if [ -n "$ERR_CONTENT" ]; then + write_color_output "[DEBUG] Error log: $ERR_CONTENT" "$YELLOW" + fi + fi + sleep 0.5 +done + +if [ "$SERVER_STARTED" = false ]; then + write_color_output "[ERROR] Python server failed to start" "$RED" + write_color_output "[DEBUG] Logs:" "$YELLOW" + [ -f "$PY_LOG_FILE" ] && cat "$PY_LOG_FILE" | while read line; do write_color_output "$line" "$YELLOW"; done + [ -f "$PY_ERR_LOG_FILE" ] && cat "$PY_ERR_LOG_FILE" | while read line; do write_color_output "$line" "$YELLOW"; done + [ -f "$PY_DEBUG_LOG_FILE" ] && cat "$PY_DEBUG_LOG_FILE" | while read line; do write_color_output "$line" "$YELLOW"; done + write_color_output "[DEBUG] Trying foreground run..." "$YELLOW" + $PYTHON_CMD -u ping.py server --port 8000 + exit 1 +fi + +# Extract Peer ID +PEER_ID="" +MULTI_ADDR="" +if [ -f "$PY_LOG_FILE" ]; then + CONTENT=$(cat "$PY_LOG_FILE" 2>/dev/null || true) + PEER_ID=$(echo "$CONTENT" | grep -oP "Peer ID:\s*\K[A-Za-z0-9]+" || true) + if [ -n "$PEER_ID" ]; then + MULTI_ADDR="/ip4/127.0.0.1/tcp/8000/p2p/$PEER_ID" + write_color_output "[OK] Peer ID: $PEER_ID" "$CYAN" + write_color_output "[OK] MultiAddr: $MULTI_ADDR" "$CYAN" + fi +fi + +if [ -z "$PEER_ID" ]; then + write_color_output "[ERROR] Could not extract Peer ID" "$RED" + [ -f "$PY_LOG_FILE" ] && cat "$PY_LOG_FILE" | while read line; do write_color_output "$line" "$YELLOW"; done + [ -f "$PY_ERR_LOG_FILE" ] && cat "$PY_ERR_LOG_FILE" | while read line; do write_color_output "$line" "$YELLOW"; done + [ -f "$PY_DEBUG_LOG_FILE" ] && cat "$PY_DEBUG_LOG_FILE" | while read line; do write_color_output "$line" "$YELLOW"; done + kill $PY_PROCESS_PID 2>/dev/null || true + exit 1 +fi + +# Start JavaScript client +write_color_output "[JAVASCRIPT] Starting client..." "$YELLOW" +cd ../js_node + +JS_LOG_FILE="test_js_client_to_py_server.log" +JS_ERR_LOG_FILE="test_js_client_to_py_server.log.err" + +rm -f "$JS_LOG_FILE" "$JS_ERR_LOG_FILE" + +node src/ping.js client "$MULTI_ADDR" 3 > "$JS_LOG_FILE" 2> "$JS_ERR_LOG_FILE" & +JS_PROCESS_PID=$! + +write_color_output "[DEBUG] JavaScript client PID: $JS_PROCESS_PID" "$BLUE" +write_color_output "[DEBUG] Client logs: $(pwd)/$JS_LOG_FILE, $(pwd)/$JS_ERR_LOG_FILE" "$BLUE" + +# Wait for client to complete +CLIENT_TIMEOUT=10 +CLIENT_START=$(date +%s) +while kill -0 $JS_PROCESS_PID 2>/dev/null && [ $(($(date +%s) - CLIENT_START)) -lt $CLIENT_TIMEOUT ]; do + sleep 1 +done + +if kill -0 $JS_PROCESS_PID 2>/dev/null; then + write_color_output "[DEBUG] JavaScript client did not exit, terminating..." "$YELLOW" + kill $JS_PROCESS_PID 2>/dev/null || true +fi + +write_color_output "[CHECK] Results..." "$CYAN" +SUCCESS=false +if [ -f "$JS_LOG_FILE" ]; then + JS_LOG_CONTENT=$(cat "$JS_LOG_FILE" 2>/dev/null || true) + if echo "$JS_LOG_CONTENT" | grep -q "successful\|Ping.*successful"; then + SUCCESS=true + write_color_output "[SUCCESS] Ping test passed" "$GREEN" + else + write_color_output "[FAILED] No successful pings" "$RED" + write_color_output "[DEBUG] Client log path: $(pwd)/$JS_LOG_FILE" "$YELLOW" + write_color_output "Client log:" "$YELLOW" + write_color_output "$JS_LOG_CONTENT" "$YELLOW" + if [ -f "$JS_ERR_LOG_FILE" ]; then + write_color_output "[DEBUG] Client error log path: $(pwd)/$JS_ERR_LOG_FILE" "$YELLOW" + write_color_output "Client error log:" "$YELLOW" + cat "$JS_ERR_LOG_FILE" | while read line; do write_color_output "$line" "$YELLOW"; done + fi + write_color_output "[DEBUG] Python server log path: $(pwd)/../py_node/$PY_LOG_FILE" "$YELLOW" + write_color_output "Python server log:" "$YELLOW" + if [ -f "../py_node/$PY_LOG_FILE" ]; then + PY_LOG_CONTENT=$(cat "../py_node/$PY_LOG_FILE" 2>/dev/null || true) + if [ -n "$PY_LOG_CONTENT" ]; then + write_color_output "$PY_LOG_CONTENT" "$YELLOW" + else + write_color_output "Empty or inaccessible" "$YELLOW" + fi + else + write_color_output "File not found" "$YELLOW" + fi + write_color_output "[DEBUG] Python server error log path: $(pwd)/../py_node/$PY_ERR_LOG_FILE" "$YELLOW" + write_color_output "Python server error log:" "$YELLOW" + if [ -f "../py_node/$PY_ERR_LOG_FILE" ]; then + PY_ERR_LOG_CONTENT=$(cat "../py_node/$PY_ERR_LOG_FILE" 2>/dev/null || true) + if [ -n "$PY_ERR_LOG_CONTENT" ]; then + write_color_output "$PY_ERR_LOG_CONTENT" "$YELLOW" + else + write_color_output "Empty or inaccessible" "$YELLOW" + fi + else + write_color_output "File not found" "$YELLOW" + fi + write_color_output "[DEBUG] Python debug log path: $(pwd)/../py_node/$PY_DEBUG_LOG_FILE" "$YELLOW" + write_color_output "Python debug log:" "$YELLOW" + if [ -f "../py_node/$PY_DEBUG_LOG_FILE" ]; then + PY_DEBUG_LOG_CONTENT=$(cat "../py_node/$PY_DEBUG_LOG_FILE" 2>/dev/null || true) + if [ -n "$PY_DEBUG_LOG_CONTENT" ]; then + write_color_output "$PY_DEBUG_LOG_CONTENT" "$YELLOW" + else + write_color_output "Empty or inaccessible" "$YELLOW" + fi + else + write_color_output "File not found" "$YELLOW" + fi + fi +fi + +write_color_output "[CLEANUP] Stopping processes..." "$YELLOW" +kill $PY_PROCESS_PID 2>/dev/null || true +kill $JS_PROCESS_PID 2>/dev/null || true +cd ../ + +if [ "$SUCCESS" = true ]; then + write_color_output "[SUCCESS] Test completed" "$GREEN" + exit 0 +else + write_color_output "[FAILED] Test failed" "$RED" + exit 1 +fi diff --git a/tests/interop/js_libp2p/test_js_basic.py b/tests/interop/js_libp2p/test_js_basic.py deleted file mode 100644 index f59dc4cf4..000000000 --- a/tests/interop/js_libp2p/test_js_basic.py +++ /dev/null @@ -1,5 +0,0 @@ -def test_js_libp2p_placeholder(): - """ - Placeholder test for js-libp2p interop tests. - """ - assert True, "Placeholder test for js-libp2p interop tests" From 8499405d0be426c211a34aac51845b0b91a35496 Mon Sep 17 00:00:00 2001 From: paschal533 Date: Mon, 9 Jun 2025 00:14:17 +0100 Subject: [PATCH 2/2] Remove .git --- tests/interop/js_libp2p/js_node/.gitIgnore | 1 + .../js_node/.github/pull_request_template.md | 17 ----------------- .../js_node/.github/workflows/sync.yml | 19 ------------------- 3 files changed, 1 insertion(+), 36 deletions(-) delete mode 100644 tests/interop/js_libp2p/js_node/.github/pull_request_template.md delete mode 100644 tests/interop/js_libp2p/js_node/.github/workflows/sync.yml diff --git a/tests/interop/js_libp2p/js_node/.gitIgnore b/tests/interop/js_libp2p/js_node/.gitIgnore index 59bb2a9a3..cef77aaa2 100644 --- a/tests/interop/js_libp2p/js_node/.gitIgnore +++ b/tests/interop/js_libp2p/js_node/.gitIgnore @@ -2,3 +2,4 @@ /package-lock.json /dist .log +.github \ No newline at end of file diff --git a/tests/interop/js_libp2p/js_node/.github/pull_request_template.md b/tests/interop/js_libp2p/js_node/.github/pull_request_template.md deleted file mode 100644 index b47baa1ff..000000000 --- a/tests/interop/js_libp2p/js_node/.github/pull_request_template.md +++ /dev/null @@ -1,17 +0,0 @@ -# โš ๏ธ IMPORTANT โš ๏ธ - -# Please do not create a Pull Request for this repository - -The contents of this repository are automatically synced from the parent [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) so any changes made to the standalone repository will be lost after the next sync. - -Please open a PR against [js-libp2p Examples](https://github.com/libp2p/js-libp2p-examples) instead. - -## Contributing - -Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. - -1. Fork the [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) -1. Create your Feature Branch (`git checkout -b feature/amazing-example`) -1. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`) -1. Push to the Branch (`git push origin feature/amazing-example`) -1. Open a Pull Request diff --git a/tests/interop/js_libp2p/js_node/.github/workflows/sync.yml b/tests/interop/js_libp2p/js_node/.github/workflows/sync.yml deleted file mode 100644 index 78f6c8d1e..000000000 --- a/tests/interop/js_libp2p/js_node/.github/workflows/sync.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: pull - -on: - workflow_dispatch - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Pull from another repository - uses: ipfs-examples/actions-pull-directory-from-repo@main - with: - source-repo: libp2p/js-libp2p-examples - source-folder-path: examples/${{ github.event.repository.name }} - source-branch: main - target-branch: main - git-username: github-actions - git-email: github-actions@github.com