feat(federation): advertise own_fips_npub in state snapshots
Pre-v1.4 federation pairs (who exchanged invites before fips_npub was part of the invite code) had no path to learn each other's FIPS npub — they'd stay Tor-only forever even after upgrading. Fix: every state snapshot now carries the sender's own_fips_npub, and update_node_state refreshes the stored fips_npub on the receiver side whenever it differs. - NodeStateSnapshot.own_fips_npub (serde default for back-compat). - build_local_state takes own_fips_npub alongside the other single-value fields. - handle_federation_get_state populates own_fips_npub from identity::fips_npub, with a fallback to the upstream daemon's /etc/fips/fips.pub for legacy nodes that never materialised a seed-derived key. - storage::update_node_state now writes fips_npub into the FederatedNode when a new value arrives and trims whitespace before comparing, so key rotations also flow through. - Test fixtures (storage + transport/delta + sync) updated for the new field; existing tests pass. Net effect: on the next sync, .116 and .228 learn each other's fips_npub (currently null from the old invite) and subsequent federation calls route FIPS-first automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3c83440a60
commit
bfe2603f69
@ -387,6 +387,23 @@ impl RpcHandler {
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
// Our own FIPS npub, so pre-v1.4 federation pairs (whose
|
||||
// invite codes didn't carry it) can learn it on the next sync.
|
||||
let identity_dir = self.config.data_dir.join("identity");
|
||||
let own_fips_npub = crate::identity::fips_npub(&identity_dir)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.or_else(|| {
|
||||
// Legacy/dev nodes without a seed-derived key fall back
|
||||
// to the upstream daemon's public key on disk.
|
||||
None
|
||||
});
|
||||
let own_fips_npub = match own_fips_npub {
|
||||
Some(n) => Some(n),
|
||||
None => crate::fips::service::read_upstream_npub().await.ok().flatten(),
|
||||
};
|
||||
|
||||
let state = federation::build_local_state(
|
||||
apps,
|
||||
0.0,
|
||||
@ -398,6 +415,7 @@ impl RpcHandler {
|
||||
tor_active,
|
||||
server_name,
|
||||
nostr_npub,
|
||||
own_fips_npub,
|
||||
&federated_peers,
|
||||
);
|
||||
|
||||
|
||||
@ -177,6 +177,17 @@ pub async fn update_node_state(data_dir: &Path, did: &str, state: NodeStateSnaps
|
||||
node.name = Some(name.clone());
|
||||
}
|
||||
}
|
||||
// Learn the peer's FIPS npub from their state snapshot so
|
||||
// federations established before v1.4 (pre-fips_npub) start
|
||||
// routing over FIPS on the very next sync. Refresh if the peer
|
||||
// rotated their FIPS key, too.
|
||||
if let Some(ref npub) = state.own_fips_npub {
|
||||
if !npub.is_empty()
|
||||
&& node.fips_npub.as_deref().map(str::trim) != Some(npub.trim())
|
||||
{
|
||||
node.fips_npub = Some(npub.clone());
|
||||
}
|
||||
}
|
||||
node.last_state = Some(state);
|
||||
save_nodes(data_dir, &nodes).await?;
|
||||
}
|
||||
@ -314,6 +325,7 @@ mod tests {
|
||||
uptime_secs: Some(86400),
|
||||
tor_active: Some(true),
|
||||
nostr_npub: None,
|
||||
own_fips_npub: None,
|
||||
federated_peers: Vec::new(),
|
||||
};
|
||||
|
||||
|
||||
@ -160,6 +160,7 @@ pub fn build_local_state(
|
||||
tor_active: bool,
|
||||
server_name: Option<String>,
|
||||
nostr_npub: Option<String>,
|
||||
own_fips_npub: Option<String>,
|
||||
federated_peers: &[FederatedNode],
|
||||
) -> NodeStateSnapshot {
|
||||
let hints = federated_peers
|
||||
@ -186,6 +187,7 @@ pub fn build_local_state(
|
||||
uptime_secs: Some(uptime),
|
||||
tor_active: Some(tor_active),
|
||||
nostr_npub,
|
||||
own_fips_npub,
|
||||
federated_peers: hints,
|
||||
}
|
||||
}
|
||||
@ -274,6 +276,7 @@ mod tests {
|
||||
true,
|
||||
Some("Test Node".to_string()),
|
||||
None,
|
||||
None,
|
||||
&[],
|
||||
);
|
||||
assert_eq!(state.apps.len(), 1);
|
||||
@ -328,7 +331,7 @@ mod tests {
|
||||
];
|
||||
let state = build_local_state(
|
||||
vec![],
|
||||
0.0, 0, 0, 0, 0, 0, true, None, None, &peers,
|
||||
0.0, 0, 0, 0, 0, 0, true, None, None, None, &peers,
|
||||
);
|
||||
assert_eq!(state.federated_peers.len(), 1);
|
||||
assert_eq!(state.federated_peers[0].did, "did:key:zTrusted");
|
||||
|
||||
@ -78,6 +78,13 @@ pub struct NodeStateSnapshot {
|
||||
/// haven't synced after this field was added will report None.
|
||||
#[serde(default)]
|
||||
pub nostr_npub: Option<String>,
|
||||
/// The sender's own FIPS npub (bech32). Lets pre-FIPS federation
|
||||
/// pairs — who federated before v1.4 added fips_npub to the invite
|
||||
/// code — discover each other's FIPS identity on the next state
|
||||
/// sync and route over FIPS from then on. Optional for back-compat
|
||||
/// with older peers.
|
||||
#[serde(default)]
|
||||
pub own_fips_npub: Option<String>,
|
||||
/// Minimal summary of peers this node trusts, used for transitive
|
||||
/// federation: when Alice syncs with Bob, she learns Bob's trusted
|
||||
/// peers and adds them as Observers on her side so `fips_npub` is
|
||||
|
||||
@ -223,6 +223,7 @@ mod tests {
|
||||
uptime_secs: Some(86400),
|
||||
tor_active: Some(true),
|
||||
nostr_npub: None,
|
||||
own_fips_npub: None,
|
||||
federated_peers: Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -256,6 +257,7 @@ mod tests {
|
||||
uptime_secs: Some(86700), // Changed
|
||||
tor_active: Some(true),
|
||||
nostr_npub: None,
|
||||
own_fips_npub: None,
|
||||
federated_peers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user