feat(messaging,dwn,mesh): route peer messaging + DWN sync + blob fetch via FIPS first

Migrates the remaining Tor-direct peer call sites to PeerRequest so
FIPS is the default when the peer is federated and running the daemon:

- node_message::send_to_peer / check_peer_reachable: gain a
  fips_npub parameter. Error messages updated to reference both
  transports.
- Callers (api/rpc/network.rs, api/rpc/peers.rs, server health
  loop): look up fips_npub from federation storage by onion and
  pass it.
- mesh::send_typed_wire_via_federation: the spawned background POST
  for the /archipelago/mesh-typed endpoint now uses PeerRequest with
  federation-resolved fips_npub. Signature domain unchanged.
- api/rpc/mesh/typed_messages.rs fetch_blob_from_peer: blob URL
  rebuilt as (base_url, path_with_query) so PeerRequest can append
  the query string after swapping the host. Cap/exp/peer
  parameters are still signed over the content ref itself, so
  transport choice is invisible to the signature.
- network/dwn_sync.rs sync_with_peers: per-peer fips_npub lookup
  before sync_single_peer; health/pull/push each dial through
  PeerRequest, so any DWN peer known to federation gets FIPS.

Left Tor-only on purpose:
- api/rpc/identity/handlers.rs handle_identity_resolve_peer_onion —
  resolving TO a DID, no anchor yet.
- content.browse / preview calls to non-federated peers fall
  through to Tor naturally inside PeerRequest (no fips_npub → skip
  FIPS branch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-19 01:36:04 -04:00
co-authored by Claude Opus 4.7
parent ba825c13a5
commit dbd19006f2
7 changed files with 112 additions and 137 deletions
@@ -725,34 +725,29 @@ impl RpcHandler {
}));
}
// Reach the sender over Tor. Onion host is used verbatim; cap/exp/peer
// match what the sender signed in handle_mesh_send_content.
let url = format!(
"http://{}/blob/{}?cap={}&exp={}&peer={}",
sender_onion
.trim_start_matches("http://")
.trim_start_matches("https://"),
cid,
cap_token,
cap_exp,
self_pubkey_hex,
// Reach the sender: FIPS preferred when the sender is federated
// and has advertised a FIPS npub, Tor fallback otherwise.
// Cap/exp/peer in the query string match what the sender signed in
// handle_mesh_send_content — signature domain unchanged.
let onion_bare = sender_onion
.trim_start_matches("http://")
.trim_start_matches("https://")
.to_string();
let path = format!(
"/blob/{}?cap={}&exp={}&peer={}",
cid, cap_token, cap_exp, self_pubkey_hex
);
let fips_npub =
crate::federation::fips_npub_for_onion(&self.config.data_dir, &onion_bare).await;
let socks_proxy = reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY)
.map_err(|e| anyhow::anyhow!("SOCKS proxy setup failed: {}", e))?;
let client = reqwest::Client::builder()
.proxy(socks_proxy)
.timeout(std::time::Duration::from_secs(120))
.build()
.map_err(|e| anyhow::anyhow!("HTTP client build failed: {}", e))?;
let resp = client
.get(&url)
.send()
.await
.map_err(|e| anyhow::anyhow!("Fetch failed: {}", e))?;
let (resp, transport) =
crate::fips::dial::PeerRequest::new(fips_npub.as_deref(), &onion_bare, &path)
.timeout(std::time::Duration::from_secs(120))
.send_get()
.await
.map_err(|e| anyhow::anyhow!("Fetch failed: {}", e))?;
if !resp.status().is_success() {
anyhow::bail!("Blob fetch HTTP {}", resp.status());
anyhow::bail!("Blob fetch HTTP {} (via {})", resp.status(), transport);
}
let mime = resp
.headers()
@@ -777,7 +772,7 @@ impl RpcHandler {
"/blob/{}?cap={}&exp={}&peer={}",
meta.cid, local_cap, local_exp, self_pubkey_hex
);
info!(cid = %cid, size = meta.size, "Fetched content_ref blob via tor");
info!(cid = %cid, size = meta.size, transport = %transport, "Fetched content_ref blob");
Ok(serde_json::json!({
"fetched": true,
"cached": false,