note-251110-libp2p-first-implementation-learnings
libp2p First Implementation - Early Learnings
Date: 2025-11-10
Issue: - libp2p Integration
Status: 🔄 In Progress - Initial Implementation
Context
Building the first iteration of the P2P utility process with libp2p. This document captures early learnings and blockers discovered during initial implementation.
Learning: Dial Requires Multiaddr, Not Just PeerID
Discovery: When implementing connectToPeer(), realized that libp2p's dial() method requires a full multiaddr, not just a peer ID string.
The Problem:
// This doesn't work:
await libp2pNode.dial('12D3KooWFoo...');
// This works:
await libp2pNode.dial('/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWFoo...');
Why It Matters:
Our whtnxt://connect/<peerId> protocol URLs only contain the peer ID, not the full multiaddr (IP address, port, transport protocol). We need a way to resolve peer IDs to multiaddrs.
Solutions (in order of implementation priority):
-
mDNS Discovery (Immediate):
- When peer is discovered via mDNS, libp2p's
peerStoresaves their multiaddrs - We can retrieve multiaddrs from peerStore:
await libp2pNode.peerStore.get(peerId) - Limitation: Only works for peers discovered on local network
- When peer is discovered via mDNS, libp2p's
-
Relay/Circuit Relay (Phase 2):
- Include relay multiaddr in protocol URL:
whtnxt://connect/<peerId>?relay=/ip4/… - Connect to relay, relay brokers connection to target peer
- Trade-off: Requires relay infrastructure
- Include relay multiaddr in protocol URL:
-
DHT Peer Routing (Phase 3):
- libp2p's DHT (Distributed Hash Table) can find peers globally
- Query DHT for peer's multiaddrs:
await libp2pNode.peerRouting.findPeer(peerId) - Trade-off: Requires DHT bootstrap nodes, adds complexity
Decision for MVP: Start with mDNS-only (local network), add relay in Phase 2.
Learning: PeerID String Vs libp2p PeerId Object
Discovery: libp2p's TypeScript APIs expect PeerId objects, not plain strings.
The Problem:
// Our protocol uses strings:
const peerId: string = '12D3KooWFoo...';
// libp2p expects PeerId objects:
import { peerIdFromString } from '@libp2p/peer-id';
const peerIdObj = peerIdFromString(peerId);
await libp2pNode.dial(peerIdObj);
Why It Matters:
Need conversion utilities between our string-based protocol and libp2p's object-based APIs.
Action Items:
- Add
@libp2p/peer-idpackage for conversion utilities - Create helper functions in
/shared/core/protocol.ts:stringToPeerId(str: string): PeerIdpeerIdToString(peerId: PeerId): string
Learning: WebRTC Transport Configuration
Discovery: libp2p's WebRTC transport requires additional configuration for desktop-to-desktop connections.
Initial Configuration:
transports: [webRTC()],
Blocker: This may not work out-of-box for Electron utility process. Need to investigate:
- Does
@libp2p/webrtcsupport Node.js environment? - Do we need
wrtc(WebRTC polyfill for Node.js)? - Should we use
@libp2p/webrtc-directinstead?
Status: ⚠️ BLOCKER - Need to test if WebRTC transport works in utility process.
Action Items:
- Test minimal libp2p node startup in utility process
- Check if WebRTC requires browser APIs (fails in Node.js)
- Research
@libp2p/webrtc-directvs@libp2p/webrtc - Consider adding
@libp2p/tcpand@libp2p/websocketsas fallback transports
Learning: mDNS Peer Filtering
Discovery: mDNS will discover ALL libp2p peers on local network, not just WhatNext instances.
The Problem:
If IPFS Desktop or other libp2p apps are running on the same network, we'll discover them too. Users might see irrelevant peers in the UI.
Solutions:
-
Protocol Matching (Recommended):
- When peer is discovered, check if they support our custom protocol
- Query:
await libp2pNode.peerStore.protoBook.get(peerId) - Only show peers that support
/whatnext/1.0.0protocol - Trade-off: Requires opening connection to check protocol support
-
Service Name Filtering:
- Configure mDNS with custom service name:
_whatnext._udp.local - Only WhatNext instances advertise this service
- Trade-off: May require custom mDNS implementation
- Configure mDNS with custom service name:
-
Post-Connection Handshake:
- Accept all discovered peers initially
- After connection, send WhatNext handshake message
- If peer doesn't respond with valid handshake, disconnect
- Trade-off: Wastes resources connecting to non-WhatNext peers
Decision for MVP: Option 3 (post-connection handshake) - simplest to implement.
Learning: Utility Process Vs Worker Threads
Discovery: Electron's utilityProcess API uses worker threads under the hood.
What This Means:
- Our P2P service imports
parentPortfromnode:worker_threads - Communication happens via
parentPort.postMessage()(like Web Workers) - Not traditional
process.send()like child processes
Electron API:
// In main.ts
import { utilityProcess } from 'electron';
const p2pProcess = utilityProcess.fork('/path/to/p2p-service.js');
p2pProcess.postMessage({ type: 'START_NODE' });
p2pProcess.on('message', (message) => {
console.log('Received from utility:', message);
});
Action Items:
- Update main.ts to spawn utility process
- Test MessagePort communication
- Handle utility process crashes/restarts
Learning: Build Configuration for Utility Process
Discovery: The utility process needs to be bundled separately from main/renderer.
Current Build Setup:
tsupbuilds main.ts and preload.ts toapp/dist/- Vite builds renderer to
app/dist/
Needed:
- Utility process must be built as a separate entry point
- Output:
app/dist/p2p-service.js - Must include all libp2p dependencies (large bundle)
Action Items:
- Update
tsup.config.tsto include utility process entry point - Test that bundled utility process works when spawned
- Consider code splitting to reduce bundle size
Next Steps
- Test libp2p node startup in utility process (validate WebRTC works in Node.js)
- Update build configuration to bundle utility process
- Implement utility process spawning in main.ts
- Add peer ID conversion utilities (string ↔ PeerId object)
- Test mDNS discovery with two Electron instances on same network
Open Questions
- WebRTC in Node.js: Does
@libp2p/webrtcwork in Node.js utility process, or do we needwrtcpolyfill? - Transport Fallbacks: Should we add TCP/WebSocket transports for robustness?
- PeerStore Persistence: Does libp2p's peerStore persist across restarts, or is it in-memory only?
- Connection Limits: What's a realistic max peer count for collaborative playlists? (Current: 10)
- Protocol Versioning: How do we handle future protocol changes? (e.g.,
/whatnext/2.0.0)
References
- libp2p Dialing
- libp2p PeerStore
- libp2p WebRTC Transport
- Electron utilityProcess
- Issue: Handle
whtnxt://connectCustom Protocol