From 51e8c8e38c05d98487784e21f9df54cc18875b8b Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 26 Oct 2026 05:45:20 +0100 Subject: send bounded peer lists --- src/swarm/peer/handshake.rs | 37 +++++++++++++++++++++++++++++++------ src/swarm/topology.rs | 8 +++++++- 2 files changed, 38 insertions(+), 7 deletions(-) (limited to 'src/swarm') diff --git a/src/swarm/peer/handshake.rs b/src/swarm/peer/handshake.rs index c46df9c..038dac6 100644 --- a/src/swarm/peer/handshake.rs +++ b/src/swarm/peer/handshake.rs @@ -13,6 +13,12 @@ use crate::swarm::sync::SyncEvent; use crate::swarm::topology; use crate::transport::{self, ParsedMessage}; +/// Most peers a HELLO_REPLY lists, and most we take from one: each listed peer may get +/// a HELLO, so a long list (a hub's hundreds of neighbours, or a peer making up keys) +/// costs the recipient as much as it is long. A joining node needs only a few to find +/// the swarm; the member list gives it the rest once it is ready. +const MAX_LISTED_PEERS: usize = 64; + pub(crate) async fn remember_peer(state: &AppState, peer: &str) { if peer.is_empty() { warn!("Ignoring empty peer public key"); @@ -183,6 +189,12 @@ pub(super) async fn send_hello_reply( peers.push(known); } } + // Those the recipient would most likely pick as neighbours itself (lowest pair + // score with it, as `topology` ranks them), if there are too many. + if peers.len() > MAX_LISTED_PEERS { + peers.sort_by_cached_key(|known| topology::pair_score(peer, known)); + peers.truncate(MAX_LISTED_PEERS); + } let auth = state .swarm_key @@ -215,18 +227,31 @@ pub(super) async fn send_hello_reply( Ok(()) } +/// Greets the peers a HELLO_REPLY listed: at most `MAX_LISTED_PEERS`, only valid +/// identities, and, once the member list is known, only those that are our neighbours +/// (it says whom we greet; a listed peer that isn't one would get nothing from us, so +/// it isn't remembered either). pub(super) async fn discover_peers(state: &AppState, from_peer: &str, advertised_peers: &[String]) { - // TODO here we could add a trace t oshwo advertised_peers + let view_known = topology::view_known(state); + let mut taken = 0; for discovered_peer in advertised_peers { - if discovered_peer == from_peer { + if discovered_peer == from_peer || !state.transport.is_peer_id(discovered_peer) { continue; } - - remember_peer(state, discovered_peer).await; - - if !topology::is_neighbour(state, discovered_peer) { + if view_known && !topology::is_neighbour(state, discovered_peer) { continue; } + if taken == MAX_LISTED_PEERS { + debug!( + from = %from_peer, + listed = advertised_peers.len(), + "HELLO_REPLY listed more peers than we take; ignoring the rest" + ); + break; + } + taken += 1; + + remember_peer(state, discovered_peer).await; if let Err(error) = send_hello_once(state, discovered_peer).await { warn!( peer = %discovered_peer, diff --git a/src/swarm/topology.rs b/src/swarm/topology.rs index 73c2f2c..e396a9d 100644 --- a/src/swarm/topology.rs +++ b/src/swarm/topology.rs @@ -39,6 +39,12 @@ pub(crate) struct NeighbourView { full_mesh: bool, } +/// Whether the neighbours are computed from the member list yet (before, everyone +/// counts as a neighbour). +pub(crate) fn view_known(state: &AppState) -> bool { + state.neighbours.read_or_recover().is_some() +} + /// Whether every reachable member is our neighbour (also while the view is unknown). pub(crate) fn is_full_mesh(state: &AppState) -> bool { state @@ -337,7 +343,7 @@ pub(crate) fn graph( } /// Symmetric: the same for (a, b) and (b, a). -fn pair_score(a: &str, b: &str) -> [u8; 32] { +pub(crate) fn pair_score(a: &str, b: &str) -> [u8; 32] { let (low, high) = if a <= b { (a, b) } else { (b, a) }; hash(PAIR_LABEL, &[low, high]) } -- cgit v1.3.1