summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-28 17:35:35 +0100
committertv <tv@krebsco.de>2026-10-28 17:35:35 +0100
commitbeaed2e572cf2477714bd4c1ea2b1901fb0cc11e (patch)
tree2dac07650feb47e7b76a06dff6fae1926fbed38e /src
parentc0f53c8d163339c675fa0336e3588b7cfa4ac87e (diff)
track unlisted members
Diffstat (limited to 'src')
-rw-r--r--src/swarm/peer/handshake.rs2
-rw-r--r--src/swarm/topology.rs30
2 files changed, 30 insertions, 2 deletions
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<String, BTreeSet<String>>,
local_transports: BTreeSet<String>,
+ /// 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<String>,
neighbours: BTreeSet<String>,
/// Every reachable member is a neighbour.
full_mesh: bool,
@@ -205,10 +208,29 @@ pub(crate) async fn refresh(state: &AppState) -> Option<NeighbourChange> {
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<NeighbourChange> {
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<NeighbourChange> {
&|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<NeighbourChange> {
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<NeighbourChange> {
.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)