Rendezvous Protocol Demo
This example demonstrates the rendezvous protocol for peer discovery in libp2p networks. The rendezvous protocol allows peers to register under namespaces and discover other peers within the same namespace, facilitating peer-to-peer communication without requiring direct connections.
Overview
The rendezvous protocol consists of two main components:
Rendezvous Server: Acts as a registry where peers can register and discover each other
Rendezvous Client: Registers with the server and discovers other peers in the same namespace
Key Features
Namespace-based Discovery: Peers register under specific namespaces for organized discovery
Automatic Refresh: Optional background refresh to maintain registrations and discovery cache
TTL Management: Time-based expiration of registrations to prevent stale entries
Peer Advertisement: Peers can advertise their presence and availability
Scalable Discovery: Efficient peer discovery without flooding the network
Quick Start
Install py-libp2p:
$ python -m pip install libp2p
Start a Rendezvous Server:
$ python rendezvous.py --mode server
2025-09-21 14:05:47,378 [INFO] [libp2p.discovery.rendezvous.service] Rendezvous service started
2025-09-21 14:05:47,378 [INFO] [rendezvous_example] Rendezvous server started with peer ID: Qmey5ZN9WjvtjzYrDfv3NYUY61tusn1qyHAWpuT5vaWUUR
2025-09-21 14:05:47,378 [INFO] [rendezvous_example] Listening on: /ip4/0.0.0.0/tcp/51302/p2p/Qmey5ZN9WjvtjzYrDfv3NYUY61tusn1qyHAWpuT5vaWUUR
2025-09-21 14:05:47,378 [INFO] [rendezvous_example] To connect a client, use:
2025-09-21 14:05:47,378 [INFO] [rendezvous_example] python rendezvous.py --mode client --address /ip4/0.0.0.0/tcp/51302/p2p/Qmey5ZN9WjvtjzYrDfv3NYUY61tusn1qyHAWpuT5vaWUUR
2025-09-21 14:05:47,378 [INFO] [rendezvous_example] Press Ctrl+C to stop...
Connect Clients (in separate terminals):
$ python rendezvous.py --mode client --address /ip4/0.0.0.0/tcp/51302/p2p/Qmey5ZN9WjvtjzYrDfv3NYUY61tusn1qyHAWpuT5vaWUUR
2025-09-21 14:07:07,641 [INFO] [rendezvous_example] Connected to rendezvous server: Qmey5ZN9WjvtjzYrDfv3NYUY61tusn1qyHAWpuT5vaWUUR
2025-09-21 14:07:07,641 [INFO] [rendezvous_example] Enable refresh: True
2025-09-21 14:07:07,641 [INFO] [rendezvous_example] 🔄 Refresh mode enabled - discovery service running in background
2025-09-21 14:07:07,642 [INFO] [rendezvous_example] Client started with peer ID: QmWyrP7nwTaDDaM4CayBybs6aATNM4CYmbmXDU6oPADN7Y
2025-09-21 14:07:07,644 [INFO] [rendezvous_example] Registering in namespace 'rendezvous'...
2025-09-21 14:07:07,645 [INFO] [rendezvous_example] ✓ Registered with TTL 7200s
2025-09-21 14:07:08,652 [INFO] [rendezvous_example] Discovering peers in namespace 'rendezvous'...
2025-09-21 14:07:08,653 [INFO] [rendezvous_example] Found self: QmWyrP7nwTaDDaM4CayBybs6aATNM4CYmbmXDU6oPADN7Y
2025-09-21 14:07:08,653 [INFO] [rendezvous_example] Total peers found: 1
2025-09-21 14:07:08,653 [INFO] [rendezvous_example] No other peers found (only self)
Usage Examples
Basic Server
Start a rendezvous server on a specific port:
$ python rendezvous.py --mode server --port 8080
Client with Custom Namespace
Register and discover peers in a custom namespace:
$ python rendezvous.py --mode client --address <server_multiaddr> --namespace "my-app"
Client without Refresh
Run a client without automatic refresh (single-shot mode):
$ python rendezvous.py --mode client --address <server_multiaddr> --refresh False
Verbose Logging
Enable debug logging for detailed information:
$ python rendezvous.py --mode server --verbose
Command Line Options
usage: rendezvous.py [-h] [--mode {server,client}] [--address [ADDRESS]]
[-p PORT] [-n NAMESPACE] [-v] [-r]
optional arguments:
-h, --help show this help message and exit
--mode {server,client}
Run as server or client
--address [ADDRESS] Server multiaddr (required for client mode)
-p PORT, --port PORT Port for server to listen on (default: random)
-n NAMESPACE, --namespace NAMESPACE
Namespace to register/discover in (default: rendezvous)
-v, --verbose Enable verbose logging
-r, --refresh Enable automatic refresh for registration and discovery cache
Protocol Flow
Server Setup: The rendezvous server starts and listens for incoming connections
Client Connection: Clients connect to the server using its multiaddr
Registration: Clients register themselves under a namespace with a TTL
Discovery: Clients query the server for other peers in the same namespace
Refresh: (Optional) Clients automatically refresh their registration before TTL expires
Unregistration: Clients cleanly unregister when shutting down
Key Components
RendezvousService
The server-side component that:
Manages peer registrations by namespace
Handles registration, unregistration, and discovery requests
Automatically cleans up expired registrations
Provides namespace statistics
RendezvousDiscovery
The client-side component that:
Registers the local peer under namespaces
Discovers other peers in namespaces
Optionally runs background refresh tasks
Manages registration TTL and cache refresh
Configuration
Default values can be customized:
from libp2p.discovery.rendezvous import config
# Default namespace for registrations
config.DEFAULT_NAMESPACE = "rendezvous"
# Default TTL for registrations (2 hours)
config.DEFAULT_TTL = 2 * 3600
# Maximum number of registrations per namespace
config.MAX_REGISTRATIONS = 1000
# Maximum TTL allowed
config.MAX_TTL = 24 * 3600 # 24 hours
Refresh Mode
When refresh mode is enabled (default), the client:
Automatically re-registers before the TTL expires (at 80% of TTL)
Refreshes the discovery cache periodically
Runs a background service using trio’s structured concurrency
Maintains long-term presence in the network
This is ideal for long-running applications that need continuous peer discovery.
Use Cases
Distributed Applications: Services that need to find each other dynamically
Gaming: Players discovering game sessions or lobbies
Content Sharing: Nodes advertising available content or services
Mesh Networks: Peers discovering neighbors in decentralized networks
Service Discovery: Microservices finding each other in P2P architectures
Error Handling
The implementation includes robust error handling:
Connection failures to rendezvous servers
Registration timeouts and failures
Discovery query errors
Background refresh task failures
Network connectivity issues
Best Practices
Use descriptive namespaces to organize different types of peers
Enable refresh mode for long-running applications
Set appropriate TTL values based on your application’s needs
Handle connection failures gracefully in production code
Monitor namespace statistics on the server for debugging
Use verbose logging during development and testing
Source Code
1#!/usr/bin/env python3
2"""
3Simple example demonstrating rendezvous protocol usage.
4
5This example shows how to:
61. Start a rendezvous service
72. Register a peer under a namespace
83. Discover other peers in the same namespace
9"""
10
11import argparse
12import logging
13from pathlib import Path
14import sys
15import traceback
16
17# Add parent directory to path to import libp2p
18sys.path.insert(0, str(Path(__file__).parent.parent.parent))
19
20import multiaddr
21import trio
22
23from libp2p import new_host
24from libp2p.discovery.rendezvous import (
25 RendezvousDiscovery,
26 RendezvousService,
27 config,
28)
29from libp2p.peer.peerinfo import PeerInfo, info_from_p2p_addr
30
31# Enable logging
32logging.basicConfig(
33 level=logging.INFO,
34 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
35 handlers=[logging.StreamHandler()],
36)
37logging.getLogger().setLevel(logging.INFO)
38logging.getLogger("libp2p.discovery.rendezvous").setLevel(logging.INFO)
39logging.getLogger("libp2p").propagate = True
40
41# Create logger for this example
42logger = logging.getLogger("rendezvous_example")
43
44
45async def run_rendezvous_server(port: int = 0):
46 """Run a rendezvous server."""
47 listen_addr = multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")
48 host = new_host()
49
50 async with host.run([listen_addr]), trio.open_nursery() as nursery:
51 # Start the peer-store cleanup task
52 nursery.start_soon(host.get_peerstore().start_cleanup_task, 60)
53
54 # Start rendezvous service
55 service = RendezvousService(host)
56
57 actual_addrs = host.get_addrs()
58 logger.info(f"Rendezvous server started with peer ID: {host.get_id()}")
59 logger.info(
60 f"Listening on: {actual_addrs[0] if actual_addrs else 'no addresses'}"
61 )
62 logger.info("To connect a client, use:")
63 if actual_addrs:
64 logger.info(
65 f" python rendezvous.py --mode client --address {actual_addrs[0]}"
66 )
67 logger.info("Press Ctrl+C to stop...")
68
69 try:
70 # Keep server running and print stats periodically
71 while True:
72 await trio.sleep(10)
73 stats = service.get_namespace_stats()
74 if stats:
75 logger.info(f"Namespace stats: {stats}")
76 else:
77 logger.info("No active registrations")
78 except KeyboardInterrupt:
79 logger.info("Shutting down rendezvous server...")
80 except Exception as e:
81 logger.error(f"Unexpected error in server: {e}")
82 raise
83
84
85async def run_client_example(
86 server_addr: str,
87 namespace: str = config.DEFAULT_NAMESPACE,
88 enable_refresh: bool = False,
89 port: int = 0,
90):
91 """Run a client that registers and discovers peers."""
92 listen_addr = multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")
93 host = new_host()
94
95 # Parse server address and extract peer info
96 try:
97 server_maddr = multiaddr.Multiaddr(server_addr)
98 server_info = info_from_p2p_addr(server_maddr)
99 except Exception as e:
100 logger.error(f"Failed to parse server address '{server_addr}': {e}")
101 return
102
103 async with host.run([listen_addr]), trio.open_nursery() as nursery:
104 # Start the peer-store cleanup task
105 nursery.start_soon(host.get_peerstore().start_cleanup_task, 60)
106
107 # Connect to server
108 try:
109 await host.connect(server_info)
110 logger.info(f"Connected to rendezvous server: {server_info.peer_id}")
111 logger.info(f"Enable refresh: {enable_refresh}")
112 except Exception as e:
113 logger.error(f"Failed to connect to server: {e}")
114 return
115
116 # Create rendezvous discovery
117 discovery = RendezvousDiscovery(host, server_info.peer_id, enable_refresh)
118
119 # Run discovery service in background if refresh is enabled
120 async with trio.open_nursery() as nursery:
121 if enable_refresh:
122 # Start the discovery service
123 nursery.start_soon(discovery.run)
124 logger.info(
125 "🔄 Refresh mode enabled - discovery service running in background"
126 )
127
128 try:
129 logger.info(f"Client started with peer ID: {host.get_id()}")
130
131 # Register under a namespace with optional auto-refresh
132 logger.info(f"Registering in namespace '{namespace}'...")
133 ttl = await discovery.advertise(namespace, ttl=config.DEFAULT_TTL)
134 logger.info(f"✓ Registered with TTL {ttl}s")
135
136 # Wait a moment for registration to propagate
137 await trio.sleep(1)
138
139 # Discover other peers
140 logger.info(f"Discovering peers in namespace '{namespace}'...")
141 peers: list[PeerInfo] = []
142 async for peer in discovery.find_peers(namespace, limit=10):
143 peers.append(peer)
144 if peer.peer_id != host.get_id():
145 logger.info(f" Found peer: {peer.peer_id}")
146 else:
147 logger.info(f" Found self: {peer.peer_id}")
148
149 logger.info(f"Total peers found: {len(peers)}")
150
151 if len(peers) > 1:
152 logger.info("✓ Successfully discovered other peers!")
153 else:
154 logger.info("No other peers found (only self)")
155
156 # Keep running for demonstration
157 if enable_refresh:
158 logger.info("Refresh mode: Registration will auto-refresh")
159 logger.info("Running for 2 minutes to demonstrate refresh...")
160 await trio.sleep(120) # 2 minutes to see refresh in action
161 else:
162 logger.info("Keeping registration active for 30 seconds...")
163 logger.info(
164 "Start another client instance to see peer discovery in action!"
165 )
166 await trio.sleep(30)
167
168 # Unregister
169 logger.info(f"Unregistering from namespace '{namespace}'...")
170 await discovery.unregister(namespace)
171 logger.info("✓ Unregistered successfully")
172
173 except Exception as e:
174 logger.error(f"Error: {e}")
175 traceback.print_exc()
176 finally:
177 # Clean up refresh tasks
178 try:
179 await discovery.close()
180 except Exception as e:
181 logger.error(f"Error closing discovery service: {e}")
182
183
184async def run(
185 mode: str,
186 address: str = "",
187 namespace: str = config.DEFAULT_NAMESPACE,
188 port: int = 0,
189 enable_refresh: bool = False,
190):
191 """Main run function."""
192 logger.debug(f"Starting in {mode} mode")
193 logger.debug(
194 f"Parameters: address={address}, namespace={namespace},"
195 f"port={port}, refresh={enable_refresh}"
196 )
197
198 if mode == "server":
199 logger.debug("Running in server mode")
200 await run_rendezvous_server(port)
201 elif mode == "client":
202 if not address:
203 logger.error("Please provide rendezvous server address")
204 logger.error("Use --address flag with server multiaddr")
205 return
206 logger.debug("Running in client mode")
207 await run_client_example(address, namespace, enable_refresh, port)
208 else:
209 logger.error(f"Unknown mode '{mode}'. Use 'server' or 'client'")
210 logger.error("Available modes: server, client")
211
212
213def main():
214 """Main function to demonstrate usage."""
215 description = """
216 Rendezvous Protocol Example
217
218 This example demonstrates the rendezvous protocol for peer discovery.
219 The rendezvous protocol allows peers to register under namespaces and
220 discover other peers in the same namespace.
221
222 Usage:
223 1. Start a rendezvous server:
224 python rendezvous.py --mode server
225
226 2. Start one or more clients (in separate terminals):
227 python rendezvous.py --mode client <server_multiaddr>
228
229 3. Enable automatic refresh for long-running clients:
230 python rendezvous.py --mode client <server_multiaddr> --refresh
231
232 Example server multiaddr: /ip4/127.0.0.1/tcp/12345/p2p/QmPeerID...
233
234 Refresh mode automatically:
235 - Re-registers the peer before TTL expires (at 80% of TTL)
236 - Refreshes discovery cache when it gets stale
237 """
238
239 parser = argparse.ArgumentParser(
240 description=description, formatter_class=argparse.RawDescriptionHelpFormatter
241 )
242
243 parser.add_argument(
244 "--mode", choices=["server", "client"], help="Run as server or client"
245 )
246
247 parser.add_argument(
248 "--address",
249 nargs="?",
250 default="",
251 help="Server multiaddr (required for client mode)",
252 )
253
254 parser.add_argument(
255 "-p",
256 "--port",
257 type=int,
258 default=0,
259 help="Port for server to listen on (default: random)",
260 )
261
262 parser.add_argument(
263 "-n",
264 "--namespace",
265 type=str,
266 default=config.DEFAULT_NAMESPACE,
267 help=f"Namespace to register/discover in (default: {config.DEFAULT_NAMESPACE})",
268 )
269
270 parser.add_argument(
271 "-v", "--verbose", action="store_true", help="Enable verbose logging"
272 )
273
274 parser.add_argument(
275 "-r",
276 "--refresh",
277 action="store_true",
278 default=False,
279 help="Enable automatic refresh for registration and discovery cache",
280 )
281
282 args = parser.parse_args()
283
284 if args.verbose:
285 # logging.getLogger().setLevel(logging.DEBUG)
286 logging.getLogger("libp2p.discovery.rendezvous").setLevel(logging.DEBUG)
287
288 try:
289 trio.run(run, args.mode, args.address, args.namespace, args.port, args.refresh)
290 except KeyboardInterrupt:
291 logger.info("Exiting...")
292
293
294if __name__ == "__main__":
295 main()
API Reference
For detailed API documentation, see:
libp2p.discovery package - Discovery protocol interfaces
libp2p.discovery.rendezvous module - Rendezvous implementation details