fix(mesh): resolve ContentRef peer via DID + name-match fallback
Mesh peer pubkeys (LoRa advert ed25519) differ from federation node pubkeys (archipelago identity), so matching on pubkey always missed and attachments >160B had no transport. Match on master DID instead; also accept an explicit peer_onion override from the frontend, which resolves the peer by display name against federation.list-nodes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -220,6 +220,12 @@ impl RpcHandler {
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing cid"))?
|
||||
.to_string();
|
||||
let caption = params["caption"].as_str().map(|s| s.to_string());
|
||||
// Optional override: frontend can resolve the peer's federation onion
|
||||
// itself (by matching mesh peer name against federation.list-nodes)
|
||||
// and pass it in directly. Bypasses the mesh-peer→DID→federation
|
||||
// lookup, which currently fails because meshcore adverts don't carry
|
||||
// the archipelago master DID.
|
||||
let explicit_peer_onion = params["peer_onion"].as_str().map(|s| s.to_string());
|
||||
|
||||
// Pull the shared blob store (set by ApiHandler at startup).
|
||||
let blob_store = {
|
||||
@@ -237,16 +243,21 @@ impl RpcHandler {
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Mesh service not running"))?;
|
||||
|
||||
// Resolve peer Ed25519 pubkey from contact_id so we can scope the cap.
|
||||
// Resolve peer Ed25519 pubkey + DID from contact_id. The pubkey scopes
|
||||
// the capability token; the DID is the stable cross-transport identity
|
||||
// used to match the peer against our federation node list (mesh and
|
||||
// federation use different ed25519 keys but the same master DID).
|
||||
let state = svc.shared_state();
|
||||
let peer_pubkey_hex = {
|
||||
let (peer_pubkey_hex, peer_did) = {
|
||||
let peers = state.peers.read().await;
|
||||
let peer = peers
|
||||
.get(&contact_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Unknown mesh contact_id {}", contact_id))?;
|
||||
peer.pubkey_hex
|
||||
let pk = peer
|
||||
.pubkey_hex
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("Peer has no pubkey yet"))?
|
||||
.ok_or_else(|| anyhow::anyhow!("Peer has no pubkey yet"))?;
|
||||
(pk, peer.did.clone())
|
||||
};
|
||||
|
||||
// Our onion (stripped of scheme/trailing slash) for the URL the receiver will hit.
|
||||
@@ -291,14 +302,21 @@ impl RpcHandler {
|
||||
// budget (cid alone is 64 hex chars, plus onion + cap). Route via
|
||||
// federation when the peer has a known onion; fall back to LoRa
|
||||
// only for tiny envelopes that could theoretically fit.
|
||||
let federation_onion = {
|
||||
// Match mesh peer → federation node by master DID, NOT by pubkey.
|
||||
// Mesh adverts carry a LoRa-local ed25519 key that differs from the
|
||||
// archipelago node's identity key in federation/nodes.json; the DID
|
||||
// is the only stable identifier the two transports share.
|
||||
let federation_onion = if let Some(onion) = explicit_peer_onion {
|
||||
Some(onion)
|
||||
} else {
|
||||
let nodes = crate::federation::load_nodes(&self.config.data_dir)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
nodes
|
||||
.into_iter()
|
||||
.find(|n| n.pubkey == peer_pubkey_hex)
|
||||
.map(|n| n.onion)
|
||||
if let Some(did) = peer_did.as_ref() {
|
||||
nodes.into_iter().find(|n| &n.did == did).map(|n| n.onion)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let msg = if let Some(onion) = federation_onion {
|
||||
svc.send_typed_wire_via_federation(
|
||||
|
||||
@@ -601,17 +601,31 @@ impl MeshService {
|
||||
wire: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let envelope = crate::mesh::message_types::TypedEnvelope::from_wire(&wire)?;
|
||||
// Find the contact_id by pubkey match; fall back to synthetic.
|
||||
// The sender's `from_pubkey_hex` is their archipelago identity key,
|
||||
// which differs from the mesh peer's LoRa advert pubkey. Resolve
|
||||
// identity → DID → mesh contact_id via federation/nodes.json (the
|
||||
// DID is the only stable cross-transport key).
|
||||
let federation_did = {
|
||||
let nodes = crate::federation::load_nodes(&self.data_dir)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
nodes
|
||||
.into_iter()
|
||||
.find(|n| n.pubkey == from_pubkey_hex)
|
||||
.map(|n| n.did)
|
||||
};
|
||||
let contact_id = {
|
||||
let peers = self.state.peers.read().await;
|
||||
peers
|
||||
.iter()
|
||||
.find_map(|(cid, p)| {
|
||||
if p.pubkey_hex.as_deref() == Some(from_pubkey_hex) {
|
||||
Some(*cid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let did_match = federation_did
|
||||
.as_ref()
|
||||
.zip(p.did.as_ref())
|
||||
.map(|(a, b)| a == b)
|
||||
.unwrap_or(false);
|
||||
let pk_match = p.pubkey_hex.as_deref() == Some(from_pubkey_hex);
|
||||
if did_match || pk_match { Some(*cid) } else { None }
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let bytes = hex::decode(from_pubkey_hex).unwrap_or_default();
|
||||
|
||||
Reference in New Issue
Block a user