This document describes the architectural changes to make the exchange truly peer-to-peer while keeping Grenache as an optional accelerator.
- Hard dependency on Grape servers - Nodes cannot function without external infrastructure
- Centralized peer discovery - All discovery goes through Grape DHT
- Single point of failure - If Grape servers fail, the network dies
- Not truly P2P - Hub-and-spoke architecture masquerading as P2P
- ✅ Nodes can function completely standalone without any external infrastructure
- ✅ Direct TCP connections between peers for true P2P communication
- ✅ Grenache is optional and only used as discovery accelerator when available
- ✅ Multiple discovery methods with automatic fallback
- ✅ Peer exchange protocol - peers share peer lists with each other
- ✅ Persistent peer list - remember peers across restarts
- ✅ No single point of failure - fully decentralized operation
Manages the state of all connected peers.
Responsibilities:
- Maintain list of active peer connections
- Track peer metadata (nodeId, address, port, capabilities)
- Health monitoring and heartbeat checks
- Automatic reconnection to disconnected peers
- Peer scoring/reputation (optional)
Key Methods:
class PeerManager {
addPeer(peer) // Add a new peer connection
removePeer(peerId) // Remove a peer
getPeer(peerId) // Get specific peer
getAllPeers() // Get all active peers
getHealthyPeers() // Get only healthy/responsive peers
persistPeers() // Save peer list to disk
loadPersistedPeers() // Load peers from disk
}Peer Data Structure:
{
nodeId: "unique-node-id",
address: "192.168.1.100",
port: 3000,
connection: <TCP Socket>,
status: "connected" | "connecting" | "disconnected",
lastSeen: Date,
lastHeartbeat: Date,
capabilities: {
version: "1.0.0",
supportedProtocols: ["order", "trade", "peerexchange"]
},
stats: {
messagesReceived: 0,
messagesSent: 0,
bytesReceived: 0,
bytesSent: 0
}
}Handles direct TCP connections between peers.
Responsibilities:
- Run TCP server to accept incoming peer connections
- Initiate outgoing connections to peers
- Protocol handshake and authentication
- Message framing and parsing
- Connection lifecycle management
Key Features:
- Bidirectional communication - Both client and server
- Message framing - Length-prefixed JSON messages
- Handshake protocol - Exchange nodeId, version, capabilities
- Keepalive/Heartbeat - Detect dead connections
- Backpressure handling - Flow control for busy peers
Protocol:
Connection Flow:
1. TCP connection established
2. Handshake: Both sides send { type: 'handshake', nodeId, version, port, capabilities }
3. Handshake ACK: Both sides respond { type: 'handshake_ack' }
4. Ready for messages
5. Periodic heartbeat: { type: 'heartbeat', timestamp }
6. Application messages: { type: 'order' | 'trade' | 'peerexchange', ... }
Message Format:
// Frame: [4 bytes length][JSON payload]
{
type: "order" | "trade" | "heartbeat" | "handshake" | "peerexchange",
from: "sender-node-id",
to: "recipient-node-id" | "*", // * for broadcast
timestamp: 1699999999999,
payload: { ... }
}Multi-strategy peer discovery system.
Discovery Strategies:
- Use Grenache DHT if available
- Fallback gracefully if Grenache unavailable
- Acts as "discovery accelerator"
- Broadcast presence on local network
- Discover peers on same LAN/WiFi
- Zero configuration for local testing
- Uses multicast DNS (like AirDrop, Chromecast)
- Connect to hardcoded/configured peer addresses
- Useful for bootstrap nodes or known peers
- Environment variable:
BOOTSTRAP_PEERS=192.168.1.100:3000,192.168.1.101:3000
- Connected peers share their peer lists
- Exponential peer discovery
- Similar to BitTorrent PEX
- Load previously successful peers from disk
- Automatic reconnection on startup
- File:
.peers.json
Discovery Priority:
1. Try persisted peers (fastest)
2. Try manual/bootstrap peers (configured)
3. Try mDNS (local network)
4. Try Grenache (if available)
5. Wait for peer exchange from connected peers
Key Methods:
class PeerDiscovery {
async discover() // Run all discovery strategies
async discoverViaGrenache() // Optional Grenache discovery
async discoverViaMDNS() // Local network discovery
async discoverViaBootstrap() // Manual peer list
async discoverViaPeerExchange() // Ask peers for more peers
async loadPersistedPeers() // Load from disk
}Intelligent message routing with fallback strategies.
Routing Logic:
1. Try direct connection (if peer connected)
2. Try Grenache (if available and peer announced)
3. Queue message for later delivery
4. Return error if all methods fail
Broadcast Strategy:
For broadcast messages (orders, trades):
1. Send to all directly connected peers (primary)
2. Send via Grenache if available (backup)
3. Dedup received messages by hash
Key Methods:
class MessageRouter {
async sendToPeer(peerId, message) // Send to specific peer
async broadcast(message) // Send to all peers
async route(message) // Intelligent routing
}Defines the peer-to-peer communication protocol.
Message Types:
// Handshake
{
type: 'handshake',
nodeId: 'node-123',
version: '1.0.0',
port: 3000,
capabilities: ['order', 'trade', 'peerexchange']
}
// Heartbeat
{
type: 'heartbeat',
timestamp: 1699999999999
}
// Peer Exchange
{
type: 'peerexchange',
peers: [
{ nodeId: 'node-456', address: '192.168.1.100', port: 3000 },
{ nodeId: 'node-789', address: '192.168.1.101', port: 3001 }
]
}
// Order
{
type: 'order',
order: { ... }
}
// Trade
{
type: 'trade',
trade: { ... }
}Integration of hybrid P2P system.
Changes:
class ExchangeClient {
#peerManager
#directConnectionService
#peerDiscovery
#messageRouter
#grenacheService // Optional now!
async initialize() {
// 1. Start direct connection service (always)
await this.#directConnectionService.start();
// 2. Try to initialize Grenache (optional)
try {
await this.#grenacheService.initialize();
this.#hasGrenache = true;
} catch (err) {
logger.warn('Grenache not available, running in pure P2P mode');
this.#hasGrenache = false;
}
// 3. Discover peers using all strategies
await this.#peerDiscovery.discover();
}
async submitOrder(order) {
// Match locally first
const trades = this.#orderbook.matchOrder(order);
// Broadcast via hybrid router
await this.#messageRouter.broadcast({
type: 'order',
order: order
});
}
}src/
├── p2p/
│ ├── peer-manager.js # Manages peer connections
│ ├── direct-connection-service.js # TCP server/client
│ ├── peer-discovery.js # Multi-strategy discovery
│ ├── message-router.js # Intelligent message routing
│ ├── peer-protocol.js # Protocol definitions
│ └── peer-storage.js # Persist peers to disk
├── clients/
│ └── exchange-client.js # Modified to use hybrid P2P
├── services/
│ └── grenache-service.js # Now optional!
└── core/
└── orderbook.js # No changes needed
# Direct P2P Configuration
P2P_ENABLED=true # Enable direct P2P (default: true)
P2P_PORT=3000 # TCP port for peer connections
P2P_HOST=0.0.0.0 # Bind address
# Discovery Configuration
DISCOVERY_MDNS=true # Enable mDNS discovery (default: true)
DISCOVERY_GRENACHE=true # Enable Grenache discovery (default: true)
BOOTSTRAP_PEERS= # Comma-separated peer addresses
# Peer Management
PEER_STORAGE_PATH=.peers.json # Where to save peer list
MAX_PEERS=50 # Maximum peer connections
PEER_RECONNECT_INTERVAL=30000 # Reconnect interval (ms)
# Grenache (Optional)
GRAPE_URL=http://127.0.0.1:30001 # Grenache URL (optional now!)P2P_ENABLED=true
DISCOVERY_GRENACHE=false
BOOTSTRAP_PEERS=192.168.1.100:3000- No dependency on Grape servers
- Relies on direct connections only
- Uses mDNS + bootstrap + peer exchange
P2P_ENABLED=true
DISCOVERY_GRENACHE=true
GRAPE_URL=http://127.0.0.1:30001- Uses Grenache for fast discovery
- Also maintains direct connections
- Best of both worlds
P2P_ENABLED=false
DISCOVERY_GRENACHE=true- Backwards compatible
- Original behavior preserved
- Implement PeerManager
- Implement DirectConnectionService
- Basic peer-to-peer communication working
- Implement PeerDiscovery
- Add mDNS support
- Add peer persistence
- Implement MessageRouter
- Modify ExchangeClient
- Make Grenache optional
- Peer exchange protocol
- Peer reputation/scoring
- NAT traversal (future)
- PeerManager connection handling
- Message framing/parsing
- Discovery strategies individually
- Pure P2P mode: 3 nodes without Grenache
- Hybrid mode: 3 nodes with Grenache available
- Failover: Start with Grenache, then kill it
- Peer exchange: Verify peers discover each other
- Local network: Multiple nodes on LAN
- Internet: Nodes across different networks (NAT)
- Partitioning: Network splits and heals
- No centralized infrastructure required
- Nodes can operate independently
- No single point of failure
- Multiple discovery methods
- Automatic failover
- Peer reconnection
- Existing Grenache infrastructure still works
- Gradual migration possible
- Legacy mode supported
- Works on LAN without configuration (mDNS)
- Works on internet with bootstrap peers
- Works with Grenache for fast discovery
- STUN/TURN servers for NAT hole punching
- UPnP port mapping
- Relay nodes for unreachable peers
- Browser-based nodes
- Direct peer connections through firewalls
- Media streaming for future features
- Full Kademlia DHT embedded in each node
- Replace Grenache completely
- Better scalability
- TLS/SSL for peer connections
- End-to-end message encryption
- Peer authentication
- Phase 1 (Core P2P): ~2-3 hours
- Phase 2 (Discovery): ~2-3 hours
- Phase 3 (Integration): ~1-2 hours
- Phase 4 (Advanced): ~2-3 hours
- Testing & Documentation: ~2 hours
Total: ~10-13 hours of development
- Should we implement all discovery strategies or start with a subset?
- Should peer persistence be encrypted?
- Do we need peer authentication/authorization?
- Should we implement NAT traversal in this phase or later?
- What's the target number of max peer connections (50? 100?)?
Once approved, implementation order:
- Create
src/p2p/directory structure - Implement PeerManager (core state management)
- Implement DirectConnectionService (TCP layer)
- Implement basic protocol (handshake + messages)
- Integrate with ExchangeClient
- Make Grenache optional
- Add discovery strategies incrementally
- Tests and documentation