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
+24 -41
View File
@@ -244,6 +244,7 @@ fn validate_onion(onion: &str) -> Result<()> {
/// derived from both nodes' ed25519 keys.
pub async fn send_to_peer(
onion: &str,
fips_npub: Option<&str>,
from_pubkey: &str,
message: &str,
signing_key: Option<&ed25519_dalek::SigningKey>,
@@ -252,13 +253,6 @@ pub async fn send_to_peer(
) -> Result<()> {
validate_onion(onion)?;
let host = if onion.ends_with(".onion") {
onion.to_string()
} else {
format!("{}.onion", onion)
};
let url = format!("http://{}/archipelago/node-message", host);
// Encrypt message if we have both keys
let (payload_message, encrypted) = match (signing_key, recipient_pubkey) {
(Some(sk), Some(rpk)) => match encrypt_for_peer(sk, rpk, message) {
@@ -281,57 +275,46 @@ pub async fn send_to_peer(
body["from_name"] = serde_json::Value::String(name.to_string());
}
let proxy =
reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?;
let client = reqwest::Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(60))
.build()
.context("Failed to build HTTP client")?;
let resp = client.post(&url).json(&body).send().await.map_err(|e| {
let (resp, transport) = crate::fips::dial::PeerRequest::new(
fips_npub,
onion,
"/archipelago/node-message",
)
.timeout(std::time::Duration::from_secs(60))
.send_json(&body)
.await
.map_err(|e| {
let msg = e.to_string();
if msg.contains("connection refused") || msg.contains("Connection refused") {
anyhow::anyhow!("Tor not reachable at 127.0.0.1:9050. Is Tor running?")
anyhow::anyhow!("Peer unreachable. Check Tor (127.0.0.1:9050) and FIPS daemon status.")
} else if msg.contains("timeout") || msg.contains("timed out") {
anyhow::anyhow!(
"Connection timed out. The peer may be offline or unreachable over Tor."
)
anyhow::anyhow!("Connection timed out. The peer may be offline.")
} else {
anyhow::anyhow!("Failed to send over Tor: {}", msg)
anyhow::anyhow!("Failed to send: {}", msg)
}
})?;
if !resp.status().is_success() {
anyhow::bail!(
"Peer returned {} {}. The peer may need /archipelago/ in its nginx config.",
"Peer returned {} {} (via {}). The peer may need /archipelago/ in its nginx config.",
resp.status().as_u16(),
resp.status().canonical_reason().unwrap_or("")
resp.status().canonical_reason().unwrap_or(""),
transport,
);
}
Ok(())
}
/// Check if a peer is reachable (ping over Tor).
pub async fn check_peer_reachable(onion: &str) -> Result<bool> {
/// Check if a peer is reachable (ping). FIPS is preferred when an npub
/// is known, Tor is the fallback.
pub async fn check_peer_reachable(onion: &str, fips_npub: Option<&str>) -> Result<bool> {
validate_onion(onion)?;
let host = if onion.ends_with(".onion") {
onion.to_string()
} else {
format!("{}.onion", onion)
};
let url = format!("http://{}/health", host);
let proxy =
reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY).context("Invalid Tor proxy")?;
let client = reqwest::Client::builder()
.proxy(proxy)
match crate::fips::dial::PeerRequest::new(fips_npub, onion, "/health")
.timeout(std::time::Duration::from_secs(30))
.build()
.context("Failed to build HTTP client")?;
match client.get(&url).send().await {
Ok(resp) => Ok(resp.status().is_success()),
.send_get()
.await
{
Ok((resp, _)) => Ok(resp.status().is_success()),
Err(_) => Ok(false),
}
}