Monitor Service
The Monitor service continuously evaluates relay health by testing NIP-11 information documents and NIP-66 capabilities. It measures round-trip times and tracks relay status over time.
Overview
Section titled “Overview”| Property | Value |
|---|---|
| Service Name | monitor |
| Lifecycle | Continuous (run_forever) |
| Default Interval | 3600 seconds (1 hour) |
| Dependencies | PostgreSQL, Tor (optional) |
| Configuration | yaml/services/monitor.yaml |
Operations
Section titled “Operations”Each cycle, the Monitor:
- Select Relays: Query relays needing health check (based on
min_age_since_check) - Connect: Establish WebSocket connections (via Tor for
.onion) - Fetch NIP-11: Request relay information document
- Test NIP-66: Test capabilities (open, read, write)
- Measure RTT: Record round-trip times for each operation
- Deduplicate: Hash NIP-11/NIP-66 content for storage efficiency
- Store Results: Insert metadata snapshots into database
Configuration
Section titled “Configuration”# Cycle intervalinterval: 3600.0 # 1 hour (minimum: 60.0)
# Tor proxy for .onion relaystor: enabled: true host: "tor" # Docker service name port: 9050
# Nostr keys for NIP-66 write testskeys: public_key: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" # private_key loaded from MONITOR_PRIVATE_KEY environment variable
# Timeouts for relay checkstimeouts: clearnet: 30.0 # seconds (5.0-120.0) tor: 60.0 # seconds (10.0-180.0)
# Concurrency settingsconcurrency: max_parallel: 50 # concurrent checks (1-500) batch_size: 50 # relays per DB batch (1-500)
# Relay selection criteriaselection: min_age_since_check: 3600 # seconds since last check (≥0)Configuration Reference
Section titled “Configuration Reference”| Field | Type | Default | Range | Description |
|---|---|---|---|---|
interval | float | 3600.0 | ≥60.0 | Seconds between cycles |
tor.enabled | bool | true | - | Enable Tor proxy |
tor.host | string | 127.0.0.1 | - | Tor SOCKS5 host |
tor.port | int | 9050 | 1-65535 | Tor SOCKS5 port |
timeouts.clearnet | float | 30.0 | 5.0-120.0 | Clearnet timeout |
timeouts.tor | float | 60.0 | 10.0-180.0 | Tor timeout |
concurrency.max_parallel | int | 50 | 1-500 | Concurrent checks |
concurrency.batch_size | int | 50 | 1-500 | DB batch size |
NIP-11 Testing
Section titled “NIP-11 Testing”The Monitor fetches the NIP-11 relay information document:
GET / HTTP/1.1Host: relay.example.comAccept: application/nostr+jsonCaptured fields:
| Field | Description |
|---|---|
name | Relay name |
description | Relay description |
pubkey | Operator’s public key |
contact | Contact information |
supported_nips | Array of supported NIPs |
software | Software name/URL |
version | Software version |
limitation | Relay limitations |
privacy_policy | Privacy policy URL |
terms_of_service | Terms of service URL |
NIP-66 Testing
Section titled “NIP-66 Testing”The Monitor tests NIP-66 relay capabilities:
Openable Test
Section titled “Openable Test”Can we establish a WebSocket connection?
→ WebSocket CONNECT wss://relay.example.com← Connection established✓ openable = true, rtt_open = 142msReadable Test
Section titled “Readable Test”Does the relay respond to REQ messages?
→ ["REQ", "test", {"limit": 1}]← ["EOSE", "test"]✓ readable = true, rtt_read = 89msWritable Test
Section titled “Writable Test”Does the relay accept EVENT messages?
→ ["EVENT", {...signed_event...}]← ["OK", "event_id", true, ""]✓ writable = true, rtt_write = 234msTor Support
Section titled “Tor Support”Monitor automatically routes .onion URLs through Tor:
if ".onion" in relay_url: connector = ProxyConnector.from_url(f"socks5://{tor_host}:{tor_port}") timeout = config.timeouts.torelse: connector = None timeout = config.timeouts.clearnetBenefits:
- Full support for Tor-only relays
- Separate, longer timeouts for Tor
- Configurable Tor proxy settings
Content Deduplication
Section titled “Content Deduplication”NIP-11 and NIP-66 documents are stored using content-addressed deduplication:
- Compute Hash: SHA-256 of the document content
- Check Existence: Does this hash already exist?
- Store or Reuse: Insert new record or reference existing
-- nip11 tableCREATE TABLE nip11 ( id BYTEA PRIMARY KEY, -- SHA-256 hash name TEXT, description TEXT, ...);
-- relay_metadata references by hashCREATE TABLE relay_metadata ( relay_url TEXT, generated_at BIGINT, nip11_id BYTEA REFERENCES nip11(id), nip66_id BYTEA REFERENCES nip66(id), ...);This means:
- Identical NIP-11 documents share one record
- Storage efficiency for relays with static metadata
- Historical tracking via
relay_metadatatimestamps
Docker Compose
Section titled “Docker Compose”monitor: command: ["python", "-m", "services", "monitor"] environment: - MONITOR_PRIVATE_KEY=${MONITOR_PRIVATE_KEY} # Optional restart: unless-stoppedManual Run
Section titled “Manual Run”cd implementations/bigbrotrpython -m services monitorWith Write Tests
Section titled “With Write Tests”MONITOR_PRIVATE_KEY=<hex_private_key> python -m services monitorDebug Mode
Section titled “Debug Mode”python -m services monitor --log-level DEBUGOutput
Section titled “Output”Typical Monitor output:
INFO monitor: cycle_startedINFO monitor: relays_selected count=1250INFO monitor: checking_relays batch=1 total=25INFO monitor: relay_checked url=wss://relay.damus.io openable=true readable=true writable=trueINFO monitor: relay_checked url=wss://nos.lol openable=true readable=true writable=falseINFO monitor: batch_completed batch=1 checked=50 success=48 failed=2...INFO monitor: cycle_completed duration=245.6 checked=1250 success=1180 failed=70INFO monitor: waiting seconds=3600Querying Results
Section titled “Querying Results”Latest Relay Status
Section titled “Latest Relay Status”SELECT relay_url, nip66_openable, nip66_readable, nip66_writable, nip66_rtt_open, nip66_rtt_read, nip11_name, nip11_softwareFROM relay_metadata_latestWHERE nip66_openable = trueORDER BY nip66_rtt_open ASCLIMIT 20;Relay Health Over Time
Section titled “Relay Health Over Time”SELECT DATE(TO_TIMESTAMP(generated_at)) as date, COUNT(*) FILTER (WHERE n66.openable) as openable, COUNT(*) FILTER (WHERE n66.readable) as readable, COUNT(*) FILTER (WHERE n66.writable) as writableFROM relay_metadata rmJOIN nip66 n66 ON rm.nip66_id = n66.idGROUP BY DATE(TO_TIMESTAMP(generated_at))ORDER BY date DESC;Software Distribution
Section titled “Software Distribution”SELECT n11.software, COUNT(DISTINCT rm.relay_url) as relay_countFROM relay_metadata_latest rmlJOIN nip11 n11 ON rml.nip11_id = n11.idWHERE n11.software IS NOT NULLGROUP BY n11.softwareORDER BY relay_count DESC;Error Handling
Section titled “Error Handling”Connection Timeout
Section titled “Connection Timeout”WARNING monitor: relay_timeout url=wss://slow-relay.example.com timeout=30.0Relay is marked as not openable; continues with other relays.
Invalid NIP-11
Section titled “Invalid NIP-11”WARNING monitor: nip11_invalid url=wss://relay.example.com error=Invalid JSONNIP-11 fields are set to NULL; NIP-66 tests still attempted.
Tor Not Available
Section titled “Tor Not Available”ERROR monitor: tor_unavailable url=wss://xyz.onion error=Connection refusedTor relays fail if Tor proxy is not running.
Performance Tuning
Section titled “Performance Tuning”High Relay Count
Section titled “High Relay Count”For large deployments with many relays:
concurrency: max_parallel: 100 # Increase parallelism batch_size: 100 # Larger DB batchesSlow Network
Section titled “Slow Network”For high-latency networks:
timeouts: clearnet: 60.0 tor: 120.0Resource Constraints
Section titled “Resource Constraints”For limited resources:
concurrency: max_parallel: 10 batch_size: 25Next Steps
Section titled “Next Steps”- Learn about Synchronizer Service
- Explore Database Tables
- Understand Configuration