JoinMarket Directory Server
Relay server for peer discovery and message routing in the JoinMarket network.
Features
- Peer Discovery: Register and discover active peers
- Message Routing: Forward public broadcasts and private messages
- Connection Management: Handle peer connections and disconnections
- Handshake Protocol: Verify peer compatibility and network
- High Performance: Async I/O with optimized message handling
- Observability: Structured logging with loguru
- Tor Hidden Service: Run behind Tor for privacy (via separate container)
Running
Docker Compose quick start (Recommended)
Use the directory_server/docker-compose.yml deployment for production-style operation
behind a Tor hidden service. The compose stack isolates the directory server on an internal
network with no direct internet access; only the Tor container bridges both internal and
external networks.
- Go to the
directory_serverdirectory. - Start Tor first with
docker compose up -d tor. - Retrieve your
.onionaddress:
cat tor/data/hostname
- Create or update
.envin the currentdirectory_server/directory with the complete endpoint so the directory can advertise the nick authentication extension and perform nick ownership authentication:
DIRECTORY_SERVER__NICK_AUTH_DIRECTORY_ID=your56characterhostname.onion:5222
- Start the directory with
docker compose up -d directory_server.
Running the whole stack before this variable is set leaves nick authentication disabled. The
directory logs a warning and its handshake does not advertise nick_auth in that state.
Docker Notes
Network Architecture
Internet <--> [tor container] <-- internal network --> [directory_server container]
directory_serverlives onjoinmarket_directory_internal(bridge, internal) -- no direct internet access, no information leakage.torbridgesjoinmarket_directory_internalandjoinmarket_directory_external, exposing the directory server only as a Tor hidden service.
Debug Image
A debug variant with pdbpp and memray pre-installed is available:
docker pull ghcr.io/joinmarket-ng/joinmarket-ng/directory-server:main-debug
Profile memory with memray:
docker run -it --rm \
-v $(pwd)/memray-output:/app/memray-output \
ghcr.io/joinmarket-ng/joinmarket-ng/directory-server:main-debug \
memray run -o /app/memray-output/profile.bin -m directory_server.main
To attach memray to a running container, add cap_add: [SYS_PTRACE] to the
service in docker-compose.yml, then:
docker exec -it joinmarket_directory_server \
python -m memray attach 1 --verbose
Installation without Docker
See Installation for general setup. For local development:
cd joinmarket-ng
source jmvenv/bin/activate # If you used install.sh
# OR create venv: python3 -m venv jmvenv && source jmvenv/bin/activate
# Install jmcore first
cd jmcore
pip install -e .
# Install directory server
cd ../directory_server
pip install -e .
# Development
pip install -e ".[dev]"
Configuration
Create a .env file or set environment variables:
# Network
NETWORK=mainnet # mainnet, testnet, signet, regtest
HOST=127.0.0.1
PORT=5222
# Server
MAX_PEERS=10000
MESSAGE_RATE_LIMIT=100
LOG_LEVEL=INFO
Heartbeat liveness settings (section [directory_server] in config.toml):
heartbeat_sweep_interval(default60.0)heartbeat_idle_threshold(default600.0)heartbeat_hard_evict(default1500.0)heartbeat_pong_wait(default30.0)
Behavior summary:
- Idle peers are probed with PING/PONG when they advertise
"ping": truein handshake features - Legacy/non-ping makers receive
!orderbookas a compatibility liveness probe - Peers idle beyond hard eviction threshold are disconnected
Nick ownership authentication settings (section [directory_server]):
nick_auth_modedefaults toprefer_verified, which authenticates upgraded clients while retaining legacy client compatibility.require_verifiedrejects legacy clients, anddisableddoes not advertise the extension.nick_auth_directory_idmust be the canonical lowercase Tor v3 endpoint, including the port, for exampleyour56characterhostname.onion:5222. Authentication is not advertised until this identity is configured. Local tests may use an explicittest:identity.nick_auth_timeoutcontrols the proof deadline and cannot exceed 30 seconds.
The directory keys relay ownership by nick. A peer's self-declared onion location is advertised connection metadata and is not reserved or treated as proof of endpoint ownership.
For local test deployments reached through an alias or forwarded port, configure
network_config.nick_auth_directory_ids on each client. The map key is the exact selected
host:port, and the value is the directory's configured test: identity. Production
.onion identities are derived from and matched exactly against the selected endpoint.
For the production Compose stack, retrieve the generated hostname first, configure the resulting
hostname.onion:5222 value as DIRECTORY_SERVER__NICK_AUTH_DIRECTORY_ID, then restart the
directory server container. Do not use the container hostname as the identity for a public Tor
endpoint.
Optional
Vanity Onion Address
Generate a vanity .onion address using
mkp224o before first start.
The tool creates one output directory per matching address and runs until killed.
Five-character prefixes are fast; six characters can take hours.
docker run --rm -it --network none -v $PWD:/keys \
ghcr.io/cathugger/mkp224o:master -d /keys desired_prefix
Copy the generated keys into the Tor data directory:
mv desired_prefix*.onion/hs_ed25519_public_key \
desired_prefix*.onion/hs_ed25519_secret_key \
desired_prefix*.onion/hostname \
tor/data/
chown -R 1000:1000 tor/data/
If the stack is already running, restart Tor to pick up the new keys:
docker compose restart tor
cat tor/data/hostname
Health Check & Monitoring
The directory server provides comprehensive health check and monitoring capabilities.
Health Check Endpoint
An HTTP server runs on port 8080 (configurable via HEALTH_CHECK_HOST and HEALTH_CHECK_PORT) providing:
GET /health - Basic health check
curl http://localhost:8080/health
# {"status": "healthy"}
GET /status - Detailed server statistics
curl http://localhost:8080/status
# {
# "network": "mainnet",
# "uptime_seconds": 3600,
# "server_status": "running",
# "max_peers": 1000,
# "stats": {
# "total_peers": 150,
# "connected_peers": 150,
# "passive_peers": 45,
# "active_peers": 105
# },
# "connected_peers": {
# "total": 150,
# "nicks": ["maker1", "taker1", ...]
# },
# "passive_peers": {
# "total": 45,
# "nicks": ["taker1", "taker2", ...]
# },
# "active_peers": {
# "total": 105,
# "nicks": ["maker1", "maker2", ...]
# },
# "active_connections": 150
# }
CLI Tool
Use jm-directory-ctl to query server status:
# Check server health
jm-directory-ctl health
# Get detailed status (human-readable)
jm-directory-ctl status
# Get status as JSON
jm-directory-ctl status --json
# Query remote server
jm-directory-ctl status --host 192.168.1.10 --port 8080
Signal-based Status Logging
Send SIGUSR1 signal to trigger detailed status logging to the server logs:
# Docker
docker kill -s SIGUSR1 joinmarket_directory_server
# Local process
kill -USR1 $(pgrep jm-directory-server)
This will log comprehensive status including: - Network type and uptime - Connected peers count and list - Passive peers (orderbook watchers/takers - NOT-SERVING-ONION) - Active peers (makers - serving onion address) - Active connections
Docker Health Check
The Docker image includes automatic health checks using the CLI command:
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD ["jm-directory-ctl", "health"]
Check container health status:
docker ps # Shows (healthy) or (unhealthy)
docker inspect joinmarket_directory_server | grep -A 10 Health
Protocol and Security Context
- Message envelope types and handshake flow: Technical Protocol Notes
- Directory role in peer discovery and routing: Technical Protocol Notes
Command Reference
jm-directory-ctl --help
usage: jm-directory-ctl [-h] [--host HOST] [--log-level LOG_LEVEL]
[--port PORT]
{health,status} ...
JoinMarket Directory Server CLI
positional arguments:
{health,status} Available commands
health Check server health
status Get server status
options:
-h, --help show this help message and exit
--host HOST Health check server host (default: 127.0.0.1)
--log-level, -l LOG_LEVEL
Log level (default: INFO)
--port PORT Health check server port (default: 8080)
jm-directory-ctl health --help
usage: jm-directory-ctl health [-h] [--json]
options:
-h, --help show this help message and exit
--json Output as JSON
jm-directory-ctl status --help
usage: jm-directory-ctl status [-h] [--json]
options:
-h, --help show this help message and exit
--json Output as JSON