diff options
| author | tv <tv@krebsco.de> | 2026-11-09 15:30:50 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-09 18:16:26 +0100 |
| commit | c4b33b9b4aff6129291021448ae21298049093ae (patch) | |
| tree | 24f7cc0d1e5c19fc6f9465d2c18304eadb5d8f0c | |
| parent | 62b2f880a1a209e396d3adaef6a38395298011c6 (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.
| -rw-r--r-- | docs/access.md | 9 | ||||
| -rw-r--r-- | src/swarm/bootstrap.rs | 1 | ||||
| -rw-r--r-- | src/swarm/roots.rs | 24 |
3 files changed, 28 insertions, 6 deletions
diff --git a/docs/access.md b/docs/access.md index 3877dff..6422b51 100644 --- a/docs/access.md +++ b/docs/access.md @@ -357,9 +357,12 @@ ID: a forged root, or a genesis started again). A node joining with an empty document takes as its root only the one it was given (`--genesis-root`), or else the one its peers name, and none while they name different ones; and none before each of its seeds named one, or ten seconds passed since the -first did. So without `--genesis-root`, the root is the one the seeds that answer within -that wait agree on: one peer hands a joiner another root only if it is all the joiner -hears by then; with `--genesis-root`, never. +first did. Its seeds are the nodes proven at the seeds' addresses: another peer naming +a root (one of a member's own identities, say) disputes it, but neither ends the wait +nor starts it. So without `--genesis-root`, the root is the one the seeds that answer +within that wait agree on, none while any peer names another: one seed hands a joiner +another root only if it is all the joiner hears of its seeds by then; with +`--genesis-root`, never. What it doesn't do yet: 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 { |
