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:

  1. Rendezvous Server: Acts as a registry where peers can register and discover each other

  2. 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

  1. Install py-libp2p:

$ python -m pip install libp2p
  1. 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...
  1. 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

  1. Server Setup: The rendezvous server starts and listens for incoming connections

  2. Client Connection: Clients connect to the server using its multiaddr

  3. Registration: Clients register themselves under a namespace with a TTL

  4. Discovery: Clients query the server for other peers in the same namespace

  5. Refresh: (Optional) Clients automatically refresh their registration before TTL expires

  6. 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

  1. Use descriptive namespaces to organize different types of peers

  2. Enable refresh mode for long-running applications

  3. Set appropriate TTL values based on your application’s needs

  4. Handle connection failures gracefully in production code

  5. Monitor namespace statistics on the server for debugging

  6. 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: