From beaed2e572cf2477714bd4c1ea2b1901fb0cc11e Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 28 Oct 2026 17:35:35 +0100 Subject: track unlisted members --- src/swarm/peer/handshake.rs | 2 ++ src/swarm/topology.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/swarm/peer/handshake.rs b/src/swarm/peer/handshake.rs index fbebfc6..3e8fbf4 100644 --- a/src/swarm/peer/handshake.rs +++ b/src/swarm/peer/handshake.rs @@ -375,6 +375,8 @@ pub(super) async fn establish_peer( peer_state.standing = Standing::Member { swarm_id: swarm_id.clone(), }; + // A member our list may lack yet: a neighbour then (`topology::refresh`). + state.topology_changed.notify_one(); } // Stop our retries and sync from a clean slate. A new handshake from the peer always diff --git a/src/swarm/topology.rs b/src/swarm/topology.rs index 2ba07b7..1e4f5b9 100644 --- a/src/swarm/topology.rs +++ b/src/swarm/topology.rs @@ -44,6 +44,9 @@ pub(crate) struct NeighbourView { /// The transports (address schemes) of each member, and ours. transports: BTreeMap>, local_transports: BTreeSet, + /// Peers that proved they are of our swarm but aren't in our member list (yet): their + /// registration hasn't reached us. They are neighbours too (`refresh`). + unlisted: BTreeSet, neighbours: BTreeSet, /// Every reachable member is a neighbour. full_mesh: bool, @@ -205,10 +208,29 @@ pub(crate) async fn refresh(state: &AppState) -> Option { unreachable.insert(member.clone()); } } + // A node that joined while the genesis node hadn't registered yet has the genesis + // document without the genesis node in it, and registers itself there: then neither + // lists the other, and with each the other's neighbour only by the member list, + // neither would ever sync with the other, and their registrations never pass. So a + // peer that proved itself a member of our swarm counts until the list says otherwise: + // syncing brings each the other's registration. + let mut unlisted = BTreeSet::new(); + for (peer, peer_state) in state.peers_snapshot().await { + if current.contains(&peer) || peer == state.local_id() { + continue; + } + let peer_state = peer_state.lock().await; + if matches!(peer_state.standing, crate::state::Standing::Member { .. }) + && !peer_state.is_unreachable() + { + unlisted.insert(peer); + } + } let previous = { let view = state.neighbours.read_or_recover(); if view.as_ref().is_some_and(|view| { view.members == current + && view.unlisted == unlisted && view.k == k && view.unreachable == unreachable && view.stable == stable @@ -225,7 +247,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option { Some(previous) => previous, None => state.peer_states.read().await.keys().cloned().collect(), }; - let computed = neighbours_across( + let mut computed = neighbours_across( &transports, state.local_id(), &local_transports, @@ -233,6 +255,8 @@ pub(crate) async fn refresh(state: &AppState) -> Option { &|m| unreachable.contains(m), &|m| stable.contains(m), ); + let listed_neighbours = computed.len(); + computed.extend(unlisted.iter().cloned()); let change = NeighbourChange { added: computed .iter() @@ -251,6 +275,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option { stable = stable.len(), k, neighbours = computed.len(), + unlisted = unlisted.len(), added = change.added.len(), removed = change.removed.len(), "Neighbours computed from the member list" @@ -260,13 +285,14 @@ pub(crate) async fn refresh(state: &AppState) -> Option { .filter(|m| m.as_str() != state.local_id() && !unreachable.contains(*m)) .count(); *state.neighbours.write_or_recover() = Some(NeighbourView { - full_mesh: computed.len() >= reachable_others, + full_mesh: listed_neighbours >= reachable_others, members: current, k, unreachable, stable, transports, local_transports, + unlisted, neighbours: computed, }); Some(change) -- cgit v1.3.1