feat(federation): route state-sync / invites / notifications via FIPS first
Every federation peer-to-peer call now prefers FIPS (direct ULA dial over `fips0`, ~LAN latency) and falls back to Tor only on network failure. Per-method ed25519 signatures are preserved on both transports so authenticity doesn't change. - fips::dial::PeerRequest — fluent builder that owns transport selection. Returns the Response plus the TransportKind that carried it, so handlers can log or expose which path was used. - fips::dial::is_service_active — free-standing async probe used by migration sites (the transport::fips::is_available cache is keyed to a `&self`, not usable from static contexts). - federation/sync.rs: sync_with_peer + deploy_to_peer drop the hand-rolled reqwest::Proxy dance, call PeerRequest instead. - federation/invites.rs: notify_join takes the remote's fips_npub (already parsed out of the invite code since v1.4) and dials over FIPS when available. The "peer-joined" signature domain is unchanged. - api/rpc/federation/handlers.rs: DID rotation broadcast loops over federated peers through PeerRequest; the per-peer result payload gains a `transport` field so the UI can surface mesh vs. onion. - api/rpc/tor/mod.rs: onion-address-change propagation is now the most useful FIPS-first call — fips_npub is stable across onion rotation, so peers get the new address even when the old onion is already dead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
274ed008fe
commit
1fdb5e5cf2
@@ -6,7 +6,7 @@ use crate::identity;
|
||||
use crate::mesh;
|
||||
use crate::network::dwn_store::DwnStore;
|
||||
use crate::nostr_handshake;
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
const FEDERATION_PROTOCOL: &str = "https://archipelago.dev/protocols/federation/v1";
|
||||
@@ -630,14 +630,6 @@ impl RpcHandler {
|
||||
|
||||
let nodes = federation::load_nodes(&self.config.data_dir).await?;
|
||||
|
||||
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(30))
|
||||
.build()
|
||||
.context("Failed to build HTTP client")?;
|
||||
|
||||
let mut notified = 0u32;
|
||||
let mut failed = 0u32;
|
||||
let mut results = Vec::new();
|
||||
@@ -648,13 +640,6 @@ impl RpcHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
let host = if node.onion.ends_with(".onion") {
|
||||
node.onion.clone()
|
||||
} else {
|
||||
format!("{}.onion", node.onion)
|
||||
};
|
||||
let url = format!("http://{}/rpc/v1", host);
|
||||
|
||||
let body = serde_json::json!({
|
||||
"method": "federation.peer-did-changed",
|
||||
"params": {
|
||||
@@ -666,23 +651,31 @@ impl RpcHandler {
|
||||
}
|
||||
});
|
||||
|
||||
match client.post(&url).json(&body).send().await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
let req = crate::fips::dial::PeerRequest::new(
|
||||
node.fips_npub.as_deref(),
|
||||
&node.onion,
|
||||
"/rpc/v1",
|
||||
)
|
||||
.timeout(std::time::Duration::from_secs(30));
|
||||
|
||||
match req.send_json(&body).await {
|
||||
Ok((resp, transport)) if resp.status().is_success() => {
|
||||
notified += 1;
|
||||
results.push(serde_json::json!({
|
||||
"did": node.did,
|
||||
"status": "ok",
|
||||
"transport": transport.to_string(),
|
||||
}));
|
||||
info!(peer_did = %node.did, "Notified peer of DID rotation");
|
||||
info!(peer_did = %node.did, transport = %transport, "Notified peer of DID rotation");
|
||||
}
|
||||
Ok(resp) => {
|
||||
Ok((resp, transport)) => {
|
||||
failed += 1;
|
||||
results.push(serde_json::json!({
|
||||
"did": node.did,
|
||||
"status": "error",
|
||||
"error": format!("Peer returned {}", resp.status()),
|
||||
"error": format!("Peer returned {} (via {})", resp.status(), transport),
|
||||
}));
|
||||
warn!(peer_did = %node.did, status = %resp.status(), "Peer rejected DID rotation notification");
|
||||
warn!(peer_did = %node.did, status = %resp.status(), transport = %transport, "Peer rejected DID rotation notification");
|
||||
}
|
||||
Err(e) => {
|
||||
failed += 1;
|
||||
|
||||
@@ -417,11 +417,13 @@ pub(super) async fn notify_federation_peers_address_change(
|
||||
return;
|
||||
}
|
||||
};
|
||||
let proxy = tor_proxy.unwrap_or("127.0.0.1:9050");
|
||||
// `tor_proxy` is retained for API compat but unused — the FIPS
|
||||
// fallback dial uses constants::TOR_SOCKS_PROXY internally.
|
||||
let _ = tor_proxy;
|
||||
match federation::load_nodes(data_dir).await {
|
||||
Ok(peers) => {
|
||||
for peer in peers {
|
||||
if peer.onion.is_empty() {
|
||||
if peer.onion.is_empty() && peer.fips_npub.is_none() {
|
||||
continue;
|
||||
}
|
||||
let payload = serde_json::json!({
|
||||
@@ -432,24 +434,19 @@ pub(super) async fn notify_federation_peers_address_change(
|
||||
"old_onion": old_onion,
|
||||
}
|
||||
});
|
||||
let url = format!("http://{}/rpc/v1", &peer.onion);
|
||||
let client = match reqwest::Client::builder()
|
||||
.proxy(
|
||||
match reqwest::Proxy::all(format!("socks5h://{}", proxy)).or_else(
|
||||
|_| reqwest::Proxy::all(crate::constants::TOR_SOCKS_PROXY),
|
||||
) {
|
||||
Ok(p) => p,
|
||||
Err(_) => continue,
|
||||
},
|
||||
)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(_) => continue,
|
||||
};
|
||||
match client.post(&url).json(&payload).send().await {
|
||||
Ok(_) => info!(peer_did = %peer.did, "Notified peer of address change"),
|
||||
// FIPS-preferred: peer's fips_npub is stable across
|
||||
// onion rotation, so this notification reaches them
|
||||
// even when their (or our) old onion is now stale.
|
||||
let req = crate::fips::dial::PeerRequest::new(
|
||||
peer.fips_npub.as_deref(),
|
||||
&peer.onion,
|
||||
"/rpc/v1",
|
||||
)
|
||||
.timeout(std::time::Duration::from_secs(30));
|
||||
match req.send_json(&payload).await {
|
||||
Ok((_, transport)) => {
|
||||
info!(peer_did = %peer.did, transport = %transport, "Notified peer of address change")
|
||||
}
|
||||
Err(e) => warn!(peer_did = %peer.did, "Failed to notify peer: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user