aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-09 15:30:50 +0000
committertv <tv@krebsco.de>2026-11-09 18:16:26 +0100
commitc4b33b9b4aff6129291021448ae21298049093ae (patch)
tree24f7cc0d1e5c19fc6f9465d2c18304eadb5d8f0c /src
parent62b2f880a1a209e396d3adaef6a38395298011c6 (diff)
roots: only the seeds' roots end the wait for them
Review 4, R2 partly (F8): a joining node waited for "each of its seeds" to name the swarm's root as a count, of every admitted peer that named one. Node identities are self-made and admission is the swarm key: a member answering a joiner under as many identities as it has seeds, naming its own root within the wait, made the joiner pin it before the real seeds answered, and refuse them after as another history. Now the seeds are the nodes proven at the seeds' addresses (`roots::seed_found`, as `bootstrap::resolve_seeds` finds them), and only their roots count toward the wait and start its clock. Another peer naming a root still disputes it (no root while they differ), so such a member can keep a joiner from joining, as any member can, but not hand it another root. access.md says so.
Diffstat (limited to 'src')
-rw-r--r--src/swarm/bootstrap.rs1
-rw-r--r--src/swarm/roots.rs24
2 files changed, 22 insertions, 3 deletions
diff --git a/src/swarm/bootstrap.rs b/src/swarm/bootstrap.rs
index 6d3a4ad..d2a593b 100644
--- a/src/swarm/bootstrap.rs
+++ b/src/swarm/bootstrap.rs
@@ -409,6 +409,7 @@ async fn resolve_seeds(
debug!(seed = %seed, peer = %peer, "Resolved seed");
}
remember_peer(state, &peer).await;
+ crate::swarm::roots::seed_found(state, &peer);
if let Err(error) = send_hello_once(state, &peer).await {
warn!(peer = %peer, error = %format!("{error:#}"), "Initial HELLO failed; will retry");
}
diff --git a/src/swarm/roots.rs b/src/swarm/roots.rs
index d12f089..930c190 100644
--- a/src/swarm/roots.rs
+++ b/src/swarm/roots.rs
@@ -11,8 +11,10 @@
//! * a joining node takes as its root only the one given (`--genesis-root`), or else the
//! one its peers name, and none while they name different ones (`RootPin`); and none
//! before each of its seeds named one, or `ROOT_WAIT` passed since the first did, so
-//! the seed that answers first doesn't decide alone. One peer hands a joiner another
-//! root only if it is all the joiner hears within the wait.
+//! the seed that answers first doesn't decide alone. Seeds are the nodes proven at
+//! the seeds' addresses (`seed_found`): a peer answering under identities of its own
+//! neither ends the wait nor starts its clock, only disputes. One seed hands a joiner
+//! another root only if it is all the joiner hears of its seeds within the wait.
use std::collections::{HashMap, HashSet};
use std::time::Duration;
@@ -35,6 +37,9 @@ pub(crate) const ROOT_WAIT: Duration = Duration::from_secs(10);
pub(crate) struct Roots {
/// Of the first epoch, as each peer named it.
named: HashMap<String, ChangeHash>,
+ /// The nodes proven at the seeds' addresses (`seed_found`): only theirs count
+ /// toward the wait (`repin`).
+ seeds: HashSet<String>,
/// Joining with an empty document: how many seeds it waits for, and until when once
/// the first named a root.
awaiting: Option<(usize, Option<tokio::time::Instant>)>,
@@ -156,6 +161,14 @@ pub(crate) fn await_seeds(state: &AppState, seeds: usize) {
repin(state);
}
+/// `node` is the one at a seed's address (`bootstrap::resolve_seeds`): its root counts
+/// toward the wait for the seeds (`repin`).
+pub(crate) fn seed_found(state: &AppState, node: &str) {
+ if state.roots.lock_or_recover().seeds.insert(node.to_owned()) {
+ repin(state);
+ }
+}
+
/// Until when a joining node still waits for its seeds to name the swarm's root, if it
/// waits on a clock (`await_seeds`): then `repin`.
pub(crate) fn waiting_until(state: &AppState) -> Option<tokio::time::Instant> {
@@ -172,7 +185,12 @@ pub(crate) fn repin(state: &AppState) {
let now = tokio::time::Instant::now();
let pin = {
let mut roots = state.roots.lock_or_recover();
- let named = roots.named.len();
+ // Seeds only: others' roots dispute, but neither end the wait nor start it.
+ let named = roots
+ .named
+ .keys()
+ .filter(|peer| roots.seeds.contains(*peer))
+ .count();
let awaiting = match &mut roots.awaiting {
Some((seeds, until)) => {
if until.is_none() && named > 0 {