aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-03 01:19:05 +0100
committertv <tv@krebsco.de>2026-11-03 01:19:05 +0100
commitc12ce1a67e387474d17419bb252211bf04b2e443 (patch)
tree61157a186b9a7c884859e53e8cdea3155e92ded5 /src/swarm
parent72d05e54de4a051148578d8445ad4527550052ff (diff)
always announce new heads
Diffstat (limited to 'src/swarm')
-rw-r--r--src/swarm/relay.rs42
-rw-r--r--src/swarm/topology/mod.rs12
2 files changed, 9 insertions, 45 deletions
diff --git a/src/swarm/relay.rs b/src/swarm/relay.rs
index 5ab93f2..1393d50 100644
--- a/src/swarm/relay.rs
+++ b/src/swarm/relay.rs
@@ -16,10 +16,11 @@
//! message would announce to every neighbour, and announcements would outnumber the
//! data they announce (about 15 to 1 at 250 nodes).
//!
-//! In a full mesh the writer reaches everyone itself, so nothing is announced, but to
-//! the neighbours the writer can't reach: those on none of its transports (a member on
-//! Tor alone, reached through one on Tor and libp2p, from a writer on libp2p alone).
-//! Whom it reaches is what the member list makes its neighbours (`topology::neighbours_of`).
+//! In a full mesh too: the member list makes every member the writer's neighbour, but
+//! that says nothing about whether the writer reaches them. One behind NAT may reach a
+//! single one of them, one on another transport (on Tor alone) none, and no node can
+//! tell which from where it is. A neighbour that has the changes already does nothing
+//! with an announcement but answer it.
use std::collections::{HashMap, HashSet};
use std::sync::atomic::Ordering;
@@ -44,15 +45,7 @@ const ANNOUNCE_DELAY: Duration = Duration::from_millis(200);
/// After changes from `from` were applied and our heads changed: tell our other
/// neighbours, in the next announcement round.
pub(crate) async fn announce(state: &AppState, from: &str) {
- // While the view is unknown everyone counts as a neighbour, but that isn't a full
- // mesh: the others may not hear from the writer.
- if !state.initial_sync_done.load(Ordering::Acquire)
- || (topology::view_known(state)
- && topology::is_full_mesh(state)
- && unreached(state, &HashSet::from([from.to_owned()]))
- .await
- .is_empty())
- {
+ if !state.initial_sync_done.load(Ordering::Acquire) {
return;
}
let schedule = {
@@ -101,38 +94,19 @@ async fn announce_now(state: &AppState, senders: &HashSet<String>) {
/// The neighbours to tell of the changes `senders` sent us. The peer whose changes we
/// announce has them already, so with a single sender it is skipped; with several, each
-/// lacks the others' changes. In a full mesh, also those every sender is a neighbour of:
-/// the writers reached them themselves.
+/// lacks the others' changes.
async fn unreached(state: &AppState, senders: &HashSet<String>) -> Vec<String> {
let skip = match senders.len() {
1 => senders.iter().next().cloned(),
_ => None,
};
- let full_mesh = topology::view_known(state) && topology::is_full_mesh(state);
- // Unknown (a sender not in the member list): nobody counts as reached by it.
- let reached: Vec<Option<std::collections::BTreeSet<String>>> = if full_mesh {
- senders
- .iter()
- .map(|sender| topology::neighbours_of(state, sender))
- .collect()
- } else {
- Vec::new()
- };
- let reached_by_all = |peer: &str| {
- full_mesh
- && reached.iter().zip(senders).all(|(neighbours, sender)| {
- sender == peer || neighbours.as_ref().is_some_and(|n| n.contains(peer))
- })
- };
state
.peer_states
.read()
.await
.keys()
.filter(|peer| {
- skip.as_deref() != Some(peer.as_str())
- && topology::is_neighbour(state, peer)
- && !reached_by_all(peer)
+ skip.as_deref() != Some(peer.as_str()) && topology::is_neighbour(state, peer)
})
.cloned()
.collect()
diff --git a/src/swarm/topology/mod.rs b/src/swarm/topology/mod.rs
index df75f46..e3479e3 100644
--- a/src/swarm/topology/mod.rs
+++ b/src/swarm/topology/mod.rs
@@ -67,6 +67,7 @@ pub(crate) fn view_known(state: &AppState) -> bool {
}
/// Whether every reachable member is our neighbour (also while the view is unknown).
+#[cfg(test)]
pub(crate) fn is_full_mesh(state: &AppState) -> bool {
state
.neighbours
@@ -141,17 +142,6 @@ pub(crate) fn intended_graph(state: &AppState) -> Option<BTreeMap<String, BTreeS
Some(graph)
}
-/// `member`'s neighbours as the member list makes them, as this node sees it (as in
-/// `intended_graph`); None until the view is known, or for one the list doesn't have.
-pub(crate) fn neighbours_of(state: &AppState, member: &str) -> Option<BTreeSet<String>> {
- let view = state.neighbours.read_or_recover();
- let view = view.as_ref()?;
- view.members
- .iter()
- .any(|listed| listed == member)
- .then(|| view.neighbours_of(state.local_id(), member))
-}
-
/// The transports `member` is on, by the member list (the schemes of its addresses);
/// ours as we are on them. None known: empty.
pub(crate) fn transports_of(state: &AppState, member: &str) -> BTreeSet<String> {