#1821

iOS an Android: Mismatchs on selected Nostr nodes for GEOHASH

Openwvmscsopened 4d ago

Relais des canaux geohash : mise à jour des listes sur iOS et Android

État au 26/10/2026, d'après les sources publiques de BitChat (dépôts permissionlesstech/bitchat, bitchat-android et georelays) et des mesures faites avec examples/geohash_map.rs.

En bref

  • Les messages d'un canal geohash ne vont que sur les 5 relais les plus proches du centre de la cellule. Ces relais sont pris dans une liste de relais géolocalisés.
  • iOS et Android ne lisent pas la même liste. iOS lit une copie relue à la main, mise à jour irrégulièrement. Android lit la liste brute, régénérée chaque jour.
  • Les deux apps peuvent donc choisir des relais différents pour le même canal. Un message posté depuis Android peut ne jamais atteindre un utilisateur iOS du même canal, et inversement.
  • La liste d'iOS garde aussi des relais morts que la liste brute a déjà retirés.

1. D'où vient la liste : le dépôt georelays

Le dépôt permissionlesstech/georelays produit le fichier nostr_relays.csv (une ligne hôte,latitude,longitude par relais). Une GitHub Action le régénère chaque jour, en trois étapes :

ÉtapeOutilCe qui est fait
Découvertenostr_relay_discovery.pyParcours du réseau Nostr à partir d'un relais de départ, via les listes de contacts (kind 3) et de relais (kind 10002). Chaque candidat est testé par un REQ.
Filtre BitChatfilter_bitchat_relays.sh (nak)Seuls les relais qui acceptent de lire et d'écrire un événement kind 20000 sont gardés. Le test répond aux challenges NIP-42.
Localisationrelays_geo_lookup.pyCoordonnées estimées d'après l'adresse IPv4 du relais.

Deux limites découlent de cette méthode :

  • Positions approximatives. La géolocalisation par IP renvoie souvent le centre d'une ville. Beaucoup de relais partagent donc exactement les mêmes coordonnées : par exemple 6 relais au même point de Londres (51.5072, -0.1276) et plusieurs au centre de Paris. Cela crée des égalités dans le classement par distance.
  • Relais inutilisables par l'app. Le filtre s'authentifie (NIP-42), mais l'app ne le fait pas. Un relais qui exige l'authentification passe donc le filtre tout en restant inutilisable par l'app.

2. iOS : une copie relue, mise à jour par PR

Le dépôt de l'app iOS garde sa propre copie, relays/online_relays_gps.csv.

  • Le workflow fetch_georelays.yml tourne chaque dimanche à 06:00 UTC. Il récupère nostr_relays.csv en amont (à un commit figé), puis le fait vérifier par scripts/validate_georelays.py : encodage, schéma, taille, hôtes en wss, coordonnées, doublons, ampleur de l'écart avec la version précédente.
  • S'il y a des changements, le workflow ouvre une PR qui n'est pas fusionnée automatiquement. Elle n'entre en vigueur que lorsqu'un mainteneur la relit et la fusionne.
  • Dernière mise à jour fusionnée : 26/07/2026. Au 26/10/2026, la liste iOS a donc environ trois mois de retard sur la liste en amont.

Dans l'app (GeoRelayDirectory.swift, TransportConfig.swift) :

ParamètreValeur
Source téléchargéehttps://raw.githubusercontent.com/permissionlesstech/bitchat/refs/heads/main/relays/online_relays_gps.csv
Fréquence de téléchargement24 h
Vérification de fraîcheurtoutes les heures
Nouvel essai après un échecde 1 min jusqu'à 1 h maximum
Replicache local (georelays_cache_v2.csv), puis CSV embarqué dans l'app
Réseauvia Tor si Tor est activé ; si Tor n'est pas prêt, le téléchargement est sauté (pour ne pas exposer l'IP), et l'app garde son cache

3. Android : la liste brute, directement

Dans RelayDirectory.kt :

ParamètreValeur
Source téléchargéehttps://raw.githubusercontent.com/permissionlesstech/georelays/refs/heads/main/nostr_relays.csv, la liste en amont, sans relecture
Fréquence de téléchargementquand les données ont plus de 24 h
RepliCSV embarqué dans l'app (assets)
Réseaunon vérifié

4. Récapitulatif

iOSAndroid
Liste utiliséecopie relue dans bitchatgeorelays en amont
Rythme réel des changementsà chaque PR fusionnée (dernière le 26/07/2026)quotidien
Contrôle humainouinon
Retard au 26/10/2026environ 3 moisenviron 1 jour
Égalité pour la 5ᵉ placedépartagée par nom d'hôteordre du CSV conservé

5. Conséquences pour les canaux

5.1 Les deux apps ne se retrouvent pas toujours sur les mêmes relais

Chaque app prend les 5 relais les plus proches dans sa propre liste. Dès que les deux listes diffèrent près d'une cellule, les deux sélections diffèrent aussi. Un canal n'est alors réellement partagé que par les relais communs aux deux sélections.

Mesures du 26/10/2026 (geohash_map, colonne « rang iOS / rang Android », - = non choisi) :

#u09tv (Paris) : 8 relais au total, 2 seulement communs aux deux apps.

RelaisiOSAndroidÉtat
nostr.wild-vibes.ts.net11utilisable
relay-fra.zombi.cloudrodion.com22pas de réponse en 8 s
relay.mmwaves.de4-utilisable (envoie des challenges AUTH)
nostr-relay.xbytez.io (Lille)-3utilisable
r.senx.io (Lille)-4pas de réponse en 8 s
relay.noeudlibre.fr (Lille)-5utilisable, seul relais avec du trafic (26 à 30 événements récents)
relay.cypherflow.ai3-mort : certificat d'un émetteur inconnu
treuzkas.branruz.com5-mort : certificat émis pour un autre nom

Ce qu'on en tire :

  • iOS publie sur 2 relais morts, qui ont déjà disparu de la liste en amont.
  • Il ne reste à iOS qu'un seul relais commun et opérationnel avec Android (wild-vibes).
  • Le seul relais où le canal est actif n'est choisi que par Android.

#u1 (Pays-Bas) : 9 relais au total, 3 communs. Les relais les plus actifs (nostr.stakey.net avec 104 événements, relay.mccormick.cx avec 123, test.relays.nuts.cash avec 98) sont choisis surtout, ou uniquement, par Android.

5.2 Effets visibles pour les utilisateurs

  • Messages qui n'arrivent pas d'une plateforme à l'autre : un message ne passe que par les relais choisis par les deux apps. S'ils sont en panne ou inexistants, le canal est coupé en deux.
  • Présence incomplète : les signaux de présence (kind 20001) suivent le même chemin. La liste « people » d'un utilisateur iOS peut ne pas montrer certains utilisateurs Android, et inversement.
  • Variation dans le temps : la liste Android change chaque jour, celle d'iOS par à-coups. La partie commune aux deux sélections change donc sans que les utilisateurs touchent à rien.
  • Pas de rattrapage : les kinds 20000/20001 sont éphémères. Les relais ne les stockent généralement pas, donc un message qui n'a pas atteint un relais au moment de l'envoi est perdu pour ceux qui lisent ce relais.

5.3 Effets aggravants

  • Égalités de distance : avec des coordonnées identiques, iOS et Android départagent différemment la 5ᵉ place. Les sélections peuvent donc différer même avec la même liste.
  • Relais exigeant NIP-42 : présents dans la liste, mais inutilisables par l'app (voir §1).
  • Pas de remplacement : une app ne remplace pas un relais mort par le suivant. Un relais mort réduit donc directement le nombre de relais effectifs du canal.

6. Ce que font nos outils

  • examples/geohash_map.rs affiche, pour un geohash, la sélection iOS et la sélection Android, avec le rang de chaque relais dans chacune et le résultat d'un test de lecture du canal. Le mode overlay produit une carte. Les relais en panne durable (certificat, DNS, connexion refusée, HTTP 4xx) sont écartés par défaut ; --no-drop-dead les garde.
  • examples/geohash_bot.rs lit les deux listes (--relays-csv, --android-csv) et utilise l'union des deux sélections, pour atteindre les utilisateurs des deux plateformes. Il écarte aussi par défaut les relais en panne durable.

Pour reproduire les mesures :

curl -sLO https://raw.githubusercontent.com/permissionlesstech/bitchat/main/relays/online_relays_gps.csv
curl -sLO https://raw.githubusercontent.com/permissionlesstech/georelays/main/nostr_relays.csv
cargo run --example geohash_map -- u09tv overlay

7. Pistes (pour BitChat, hors de ce crate)

  • Faire lire la même liste aux deux apps : soit Android passe sur la copie relue, soit iOS accepte la liste en amont validée automatiquement.
  • Fusionner plus souvent les PR hebdomadaires, ou les fusionner automatiquement quand le validateur ne signale qu'un écart faible.
  • Aligner les deux apps sur la même règle de départage des égalités.
  • Retirer de la liste les relais qui exigent NIP-42, puisque l'app ne fait pas d'authentification. Le filtre en amont pourrait tester sans authentification.

Sources

  • iOS : bitchat/Nostr/GeoRelayDirectory.swift, bitchat/Services/TransportConfig.swift, .github/workflows/fetch_georelays.yml, .github/workflows/validate_georelays.yml (dépôt permissionlesstech/bitchat). Historique de relays/online_relays_gps.csv : derniers commits le 31/05, 07/06, 14/06, 21/06 et 26/07/2026.
  • Android : app/src/main/java/com/bitchat/android/nostr/RelayDirectory.kt (dépôt permissionlesstech/bitchat-android).
  • Liste en amont : README et scripts du dépôt permissionlesstech/georelays (dernière mise à jour le 25/10/2026).
  • Mesures : geohash_map, le 26/10/2026. L'état des relais change d'un jour à l'autre.

Activity

2
wvmscs4d ago

Geohash channel relays: how the lists are updated on iOS and Android

As of 2026-10-26, based on BitChat's public sources (repositories permissionlesstech/bitchat, bitchat-android and georelays) and on measurements made with examples/geohash_map.rs.

Summary

  • A geohash channel's messages go only to the 5 relays closest to the centre of the cell. These relays are taken from a list of geolocated relays.
  • iOS and Android do not read the same list. iOS reads a hand-reviewed copy that is updated irregularly. Android reads the raw list, which is regenerated every day.
  • The two apps can therefore pick different relays for the same channel. A message posted from Android may never reach an iOS user in the same channel, and vice versa.
  • The iOS list also keeps dead relays that the raw list has already removed.

1. Where the list comes from: the georelays repository

The permissionlesstech/georelays repository produces nostr_relays.csv (one host,latitude,longitude line per relay). A GitHub Action regenerates it every day, in three steps:

StepToolWhat it does
Discoverynostr_relay_discovery.pyCrawls the Nostr network from a seed relay, through contact lists (kind 3) and relay lists (kind 10002). Each candidate is tested with a REQ.
BitChat filterfilter_bitchat_relays.sh (nak)Keeps only relays that accept both reading and writing a kind 20000 event. The test answers NIP-42 challenges.
Locationrelays_geo_lookup.pyCoordinates estimated from the relay's IPv4 address.

This method has two limits:

  • Approximate positions. IP geolocation often returns a city centre. Many relays therefore share exactly the same coordinates: for example 6 relays at the same point in London (51.5072, -0.1276) and several at the centre of Paris. This creates ties in the distance ranking.
  • Relays the app cannot use. The filter authenticates (NIP-42), but the app does not. A relay that requires authentication therefore passes the filter while remaining unusable by the app.

2. iOS: a reviewed copy, updated through PRs

The iOS app's repository keeps its own copy, relays/online_relays_gps.csv.

  • The fetch_georelays.yml workflow runs every Sunday at 06:00 UTC. It fetches the upstream nostr_relays.csv (at a pinned commit), then has it checked by scripts/validate_georelays.py: encoding, schema, size, wss hosts, coordinates, duplicates, and how far it differs from the previous version.
  • If anything changed, the workflow opens a PR that is not merged automatically. It only takes effect once a maintainer reviews and merges it.
  • Last merged update: 2026-07-26. As of 2026-10-26, the iOS list is therefore about three months behind the upstream list.

In the app (GeoRelayDirectory.swift, TransportConfig.swift):

SettingValue
Downloaded sourcehttps://raw.githubusercontent.com/permissionlesstech/bitchat/refs/heads/main/relays/online_relays_gps.csv
Download frequency24 h
Freshness checkevery hour
Retry after a failurefrom 1 min up to 1 h at most
Fallbacklocal cache (georelays_cache_v2.csv), then the CSV bundled with the app
Networkthrough Tor when Tor is on; if Tor is not ready, the download is skipped (so as not to expose the IP), and the app keeps its cache

3. Android: the raw list, directly

In RelayDirectory.kt:

SettingValue
Downloaded sourcehttps://raw.githubusercontent.com/permissionlesstech/georelays/refs/heads/main/nostr_relays.csv, the upstream list, unreviewed
Download frequencywhen the data is more than 24 h old
FallbackCSV bundled with the app (assets)
Networknot checked

4. Side by side

iOSAndroid
List usedreviewed copy in bitchatupstream georelays
Actual pace of changesat each merged PR (last on 2026-07-26)daily
Human reviewyesno
Lag as of 2026-10-26about 3 monthsabout 1 day
Tie for the 5th slotbroken by host nameCSV order kept

5. Consequences for channels

5.1 The two apps don't always meet on the same relays

Each app takes the 5 closest relays from its own list. As soon as the two lists differ near a cell, the two selections differ too. A channel is then really shared only through the relays common to both selections.

Measurements from 2026-10-26 (geohash_map, "iOS rank / Android rank" columns, - = not picked):

#u09tv (Paris): 8 relays in total, only 2 shared by both apps.

RelayiOSAndroidStatus
nostr.wild-vibes.ts.net11usable
relay-fra.zombi.cloudrodion.com22no answer within 8 s
relay.mmwaves.de4-usable (sends AUTH challenges)
nostr-relay.xbytez.io (Lille)-3usable
r.senx.io (Lille)-4no answer within 8 s
relay.noeudlibre.fr (Lille)-5usable, the only relay with traffic (26 to 30 recent events)
relay.cypherflow.ai3-dead: certificate from an unknown issuer
treuzkas.branruz.com5-dead: certificate issued for another name

What this shows:

  • iOS publishes to 2 dead relays that have already disappeared from the upstream list.
  • iOS has only one shared, working relay left with Android (wild-vibes).
  • The only relay where the channel is active is picked by Android alone.

#u1 (Netherlands): 9 relays in total, 3 shared. The busiest relays (nostr.stakey.net with 104 events, relay.mccormick.cx with 123, test.relays.nuts.cash with 98) are picked mostly, or only, by Android.

5.2 Effects users see

  • Messages that don't cross platforms: a message only reaches the other platform through the relays both apps picked. If those are down or there are none, the channel is split in two.
  • Incomplete presence: presence signals (kind 20001) take the same path. An iOS user's "people" list may miss some Android users, and vice versa.
  • Changes over time: the Android list changes every day, the iOS list in jumps. The overlap between the two selections therefore shifts without users doing anything.
  • No catching up: kinds 20000/20001 are ephemeral. Relays generally don't store them, so a message that did not reach a relay when it was sent is lost for those reading that relay.

5.3 Aggravating factors

  • Distance ties: with identical coordinates, iOS and Android break the tie for the 5th slot differently. The selections can therefore differ even with the same list.
  • Relays requiring NIP-42: present in the list, but unusable by the app (see §1).
  • No replacement: an app does not replace a dead relay with the next one. Each dead relay directly reduces the number of relays the channel actually has.

6. What our tools do

  • examples/geohash_map.rs shows, for a geohash, the iOS selection and the Android selection, with each relay's rank in each and the result of a channel read test. The overlay mode produces a map. Relays that are down for good (certificate, DNS, connection refused, HTTP 4xx) are set aside by default; --no-drop-dead keeps them.
  • examples/geohash_bot.rs reads both lists (--relays-csv, --android-csv) and uses the union of both selections, to reach users of both platforms. It also sets aside relays that are down for good by default.

To reproduce the measurements:

curl -sLO https://raw.githubusercontent.com/permissionlesstech/bitchat/main/relays/online_relays_gps.csv
curl -sLO https://raw.githubusercontent.com/permissionlesstech/georelays/main/nostr_relays.csv
cargo run --example geohash_map -- u09tv overlay

7. Suggestions (for BitChat, outside this crate)

  • Have both apps read the same list: either Android switches to the reviewed copy, or iOS accepts the automatically validated upstream list.
  • Merge the weekly PRs more often, or merge them automatically when the validator reports only a small change.
  • Align both apps on the same tie-breaking rule.
  • Remove relays that require NIP-42 from the list, since the app does not authenticate. The upstream filter could test without authentication.

Sources

  • iOS: bitchat/Nostr/GeoRelayDirectory.swift, bitchat/Services/TransportConfig.swift, .github/workflows/fetch_georelays.yml, .github/workflows/validate_georelays.yml (repository permissionlesstech/bitchat). History of relays/online_relays_gps.csv: latest commits on 2026-05-31, 06-07, 06-14, 06-21 and 07-26.
  • Android: app/src/main/java/com/bitchat/android/nostr/RelayDirectory.kt (repository permissionlesstech/bitchat-android).
  • Upstream list: README and scripts of the permissionlesstech/georelays repository (last updated on 2026-10-25).
  • Measurements: geohash_map, on 2026-10-26. Relay status changes from one day to the next.
wvmscs3d ago

When was the file currently served for download last updated?

The data dates from 2026-07-25 at 08:42 UTC. The online file (SHA-256 81152310…) is byte-for-byte identical to nostr_relays.csv in upstream georelays commit eb5fc78. It is a snapshot of relay status at that moment.

It was published to main on 2026-07-26 at 06:54 UTC by bitchat commit fb8fe397. That was the last successful sync, made just before updates moved to reviewed pull requests (#1436, the same day).

The file users download is therefore about 3 months old (94 days). Over that time, georelays has kept publishing a new snapshot almost every day; the most recent is from 2026-10-26.

Risk: the margin is shrinking

The app compares each new file with the one it already has, here the CSV bundled on 2026-07-26. With the current upstream file, 175 identical entries are retained against 163 required: only 12 entries of margin remain. If the merge is delayed further and relays keep changing, a future file could fall below 50%. v1.8.0 would then reject it indefinitely, retrying periodically, until a new app release ships.

So #1738 or the automation branch should be merged soon, and issue #1724 then closed. The automation should also be allowed to create pull requests; otherwise the blockage will come back every week.


Date de mise à jour du fichier actuellement proposé au téléchargement?

Les données datent du 25/07/2026 à 08:42 UTC. Le fichier en ligne (SHA-256 81152310…) est identique, octet pour octet, au nostr_relays.csv du commit amont georelays eb5fc78. C'est le relevé de l'état des relais à ce moment-là.

Il a été publié sur main le 26/07/2026 à 06:54 UTC, par le commit bitchat fb8fe397. C'est la dernière synchronisation réussie, faite juste avant le passage aux PR soumises à relecture (#1436, le même jour).

Le fichier que téléchargent les utilisateurs a donc environ 3 mois (94 jours). Pendant ce temps, georelays a continué à publier un nouveau relevé presque chaque jour : le plus récent date du 26/10/2026.

Risque : la marge diminue

L'app compare chaque nouveau fichier à celui qu'elle a déjà, ici le CSV embarqué du 26/07. Avec le fichier amont actuel, 175 entrées identiques sont conservées pour 163 exigées : il ne reste que 12 entrées de marge. Si la fusion tarde encore et que les relais changent davantage, un futur fichier pourrait passer sous 50 %. La v1.8.0 le refuserait alors indéfiniment, en réessayant régulièrement, jusqu'à une nouvelle version de l'app.

Il faut donc fusionner rapidement la #1738 ou la branche d'automatisation, puis fermer l'issue #1724. Il faudrait aussi donner à l'automatisation le droit de créer des PR, sinon le blocage reviendra chaque semaine.