From 0495c44be2f9f4c1d29c5fbe1af3dd69ac9dc0b9 Mon Sep 17 00:00:00 2001 From: Taksh Date: Wed, 29 Jul 2026 11:44:41 +0300 Subject: [PATCH] docs: correct geohash publisher relay guide Describe the empty-directory fallback to default relays, clarify that sparse cells are about stale directory rows (not geographic distance), and point publishers at the canonical main-branch CSV the app refreshes. --- docs/GEOHASH-PUBLISHER-RELAYS.md | 63 ++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/docs/GEOHASH-PUBLISHER-RELAYS.md b/docs/GEOHASH-PUBLISHER-RELAYS.md index 1878232b..2a002cd7 100644 --- a/docs/GEOHASH-PUBLISHER-RELAYS.md +++ b/docs/GEOHASH-PUBLISHER-RELAYS.md @@ -15,7 +15,7 @@ Location chat messages are ephemeral Nostr events: | `kind` | `20000` (chat) or `20001` (presence heartbeat) | | `tags` | Must include `["g", ""]`. Chat may also carry `["n", ""]`. | | `content` | Message text for kind `20000`; empty for kind `20001` | -| `pubkey` | Ephemeral identity derived for that geohash (not the user's long-term key) | +| `pubkey` | Ephemeral identity derived for that geohash (not the person's long-term key) | Clients subscribe with filters equivalent to: @@ -32,25 +32,49 @@ On subscribe and publish, BitChat asks `GeoRelayDirectory` for the closest relays to the geohash center: 1. Decode the geohash to a lat/lon center. -2. Rank the bundled geo-relay directory by haversine distance. +2. Rank the geo-relay directory by haversine distance. 3. Take the nearest **5** relays (`TransportConfig.nostrGeoRelayCount`). 4. Ties break by hostname so every device with the same directory picks the same set — publishers and subscribers must agree. -With an empty or unreachable directory for that cell, the client refuses to -publish rather than falling back to unrelated default relays. +### Directory source of truth -**Implication for external publishers:** replicate the same proximity selection -(or publish to a strict superset that includes those five hosts). Publishing -only to a personal fixed relay list is the most common reason a message never -appears in the app. +Do **not** rely only on the CSV bundled inside an older app build. At runtime +the client refreshes and caches the reviewed copy on `main`: -## Sparse and ocean cells +https://raw.githubusercontent.com/permissionlesstech/bitchat/refs/heads/main/relays/online_relays_gps.csv -Some geohashes (oceans, deserts, polar regions) map to few or unreachable -nearby relays. The channel UI still opens normally — there is no separate -"undeliverable" banner — but live messages may not round-trip. Treat an empty -timeline in those cells as a relay-coverage problem, not a client bug. +That URL is `GeoRelayDirectory`'s `remoteURL`. External publishers should rank +against this same file (or a freshly synced cache of it). A snapshot shipped +with an old binary can diverge after relay GPS rows change, so messages land +on hosts current clients no longer subscribe to. + +### Empty-directory fallback + +`closestRelays` returns `[]` only when the in-memory directory has no entries +(or `count <= 0`). In that case the publish path does **not** refuse — it +falls back to `NostrRelayManager`'s default relay list (built-in + any custom +relays). See `ChatPublicConversationCoordinator.sendPublicRaw`: empty +`targetRelays` → `sendEvent(event)` with no explicit `to:`. + +Publishers should still prefer proximity selection against the live CSV. +Default-relay fallback is a last resort when the geo directory failed to load; +relying on it intentionally will miss clients that *did* load the directory +and are subscribed only to the nearest five. + +## Sparse cells (stale directory rows) + +A "sparse" cell is about **directory freshness**, not geographic remoteness. +Haversine ranking always returns the nearest five hosts from whatever GPS rows +the CSV currently has — including for oceans or deserts. When those rows are +stale or thin for a region (relay moved, GPS never updated, host offline), the +selected five can be the wrong place for live traffic even though they look +"close" on paper. + +The channel UI still opens normally — there is no separate "undeliverable" +banner — but live messages may not round-trip. Treat an empty timeline in those +cells as a stale/coverage problem in the relay directory, not a client bug. +Refreshing from the canonical CSV above is the first fix to try. ## Ephemerality and late joiners @@ -68,8 +92,12 @@ with `g` (location notes / drops) — a different surface from live channel chat 1. Compute the target geohash at the precision the channel uses (`block` ≈ 7, `neighborhood` ≈ 6, `city` ≈ 5, etc.). -2. Resolve the closest ~5 `wss://` hosts from the same geo-relay directory the - app ships (or an equivalent proximity ranking over that list). +2. Fetch the **current** + [`online_relays_gps.csv`](https://raw.githubusercontent.com/permissionlesstech/bitchat/refs/heads/main/relays/online_relays_gps.csv) + (same URL the app refreshes from). Rank by haversine to the geohash center + and take the nearest ~5 `wss://` hosts (hostname tie-break). Optionally + publish to a strict **superset** that covers both that selection and any + older bundled snapshot you still support. 3. Publish the signed kind-`20000` event to those relays with a correct `g` tag. 4. Expect no backlog for late subscribers; design tools around live delivery. 5. Do not assume App Store / mesh Bluetooth peers will see Nostr-only publishes @@ -77,7 +105,10 @@ with `g` (location notes / drops) — a different surface from live channel chat ## Related code -- `bitchat/Nostr/GeoRelayDirectory.swift` — proximity ranking +- `bitchat/Nostr/GeoRelayDirectory.swift` — proximity ranking, `remoteURL`, cache +- `bitchat/ViewModels/ChatPublicConversationCoordinator.swift` — empty-selection + fallback to default relays - `bitchat/Services/GeohashPresenceService.swift` — subscribe/publish wiring - `bitchat/Services/TransportConfig.swift` — `nostrGeoRelayCount`, lookback limits - `bitchat/Nostr/NostrProtocol.swift` — kind `20000` / `20001` builders +- `relays/online_relays_gps.csv` — reviewed directory checked into this repo