Add interoperability test for py-libp2p and js-libp2p with enhanced logging

This commit is contained in:
paschal533
2025-06-09 00:11:19 +01:00
committed by acul71
parent 505d3b2a8f
commit 1507100632
13 changed files with 1632 additions and 5 deletions

View File

@ -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 <multiaddr> [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)

View File

@ -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 <target-multiaddr> [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)
})

View File

@ -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)
})