fix(mesh): bind Reticulum peer identity directly, not via name-matching

Two MeshPeer rows were being created for one physical Reticulum node: a
radio twin (keyed by the RNS dest_hash, arch_pubkey_hex always None) and a
pseudo-federation twin (keyed by the archy ed25519 pubkey, created via the
generic identity-broadcast path meant for Meshcore/Meshtastic). The generic
path relies on bind_federation_twins matching both twins' advert_name, but
the Reticulum radio twin's display_name is deliberately never the identity
text — so the two rows could never merge, and the generic send path (keyed
off whichever twin the caller resolves) ended up looking up the archy
pubkey's prefix in ReticulumLink's `prefix_to_hash` map, which is only ever
populated with RNS dest_hash prefixes. Every send failed with "Unknown
Reticulum prefix ... peer hasn't announced yet", confirmed live between two
real nodes (archy-x250-exp / archy-x250-pa) that could see each other's
adverts but never exchange a message.

Unlike Meshcore/Meshtastic, Reticulum's ARCHY identity blob arrives in the
same announce event as the destination hash, so there's no ambiguity about
which peer it belongs to — bind it directly onto the RNS-hash-keyed radio
peer instead of relying on name-matching. Threaded through a new
`ParsedContact::arch_pubkey_hex` (None for Meshcore/Meshtastic, unchanged
behavior there) so `refresh_contacts` can set it on the correct peer row
without touching `bind_federation_twins`/`group_peer_twins`, which already
know how to collapse twins once they share an arch_pubkey_hex.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 06:18:08 +00:00
co-authored by Claude Sonnet 5
parent 893508b700
commit 458b9fbbb7
4 changed files with 50 additions and 18 deletions
+10
View File
@@ -406,6 +406,15 @@ pub struct ParsedContact {
/// contact has shared one.
pub lat: Option<f64>,
pub lon: Option<f64>,
/// Archipelago ed25519 identity hex, when this transport carried it
/// in-band with the contact announce itself (Reticulum only today — the
/// RNS announce's app_data can embed an `ARCHY:n:` identity blob
/// alongside the destination hash in the same event, so there's no
/// ambiguity about which physical peer it belongs to). Meshcore/
/// Meshtastic identity adverts go out on a separate channel and are
/// correlated after the fact by `bind_federation_twins`'s advert_name
/// matching instead, so they always leave this `None`.
pub arch_pubkey_hex: Option<String>,
}
/// Parse RESP_CONTACT (0x03) response.
@@ -457,6 +466,7 @@ pub fn parse_contact(data: &[u8]) -> Result<ParsedContact> {
snr: None,
lat: None,
lon: None,
arch_pubkey_hex: None,
})
}