From 5581992d5603322d7f02bd76625334be3f8a2b18 Mon Sep 17 00:00:00 2001 From: tv Date: Tue, 3 Nov 2026 04:44:49 +0100 Subject: measure 2-hop neighbourhood --- src/simtest/identity.rs | 1 + src/simtest/mod.rs | 1 + src/simtest/reach.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ src/simtest/timing.rs | 1 + 4 files changed, 55 insertions(+) create mode 100644 src/simtest/reach.rs (limited to 'src/simtest') diff --git a/src/simtest/identity.rs b/src/simtest/identity.rs index 7379048..154c72e 100644 --- a/src/simtest/identity.rs +++ b/src/simtest/identity.rs @@ -112,6 +112,7 @@ fn status(node: &AppState) -> ParsedMessage { leaving: false, announcing: false, eager: false, + reaches: None, }) } diff --git a/src/simtest/mod.rs b/src/simtest/mod.rs index dc23440..e9d8c15 100644 --- a/src/simtest/mod.rs +++ b/src/simtest/mod.rs @@ -13,6 +13,7 @@ mod bench; mod deltas; mod identity; mod links; +mod reach; mod relay; mod remakes; mod soak; diff --git a/src/simtest/reach.rs b/src/simtest/reach.rs new file mode 100644 index 0000000..b38db87 --- /dev/null +++ b/src/simtest/reach.rs @@ -0,0 +1,52 @@ +//! Whom neighbours reach (`swarm::reach`): told in STATUSes, as measured. + +use std::time::Duration; + +use super::Sim; +use crate::state::AppState; +use crate::swarm::bootstrap::Start; +use crate::swarm::{reach, topology}; + +const SOON: Duration = Duration::from_secs(30); + +/// Whether `node` reaches `member`, as `node` measured and told it. +fn reaches(node: &AppState, member: &AppState) -> bool { + reach::published(node).contains(&topology::pair_key(member.local_id())) +} + +/// Whether `node` heard from `neighbour` that it reaches `member`. +async fn heard(node: &AppState, neighbour: &AppState, member: &AppState) -> bool { + let Some(peer_state) = node.get_peer_state(neighbour.local_id()).await else { + return false; + }; + let said = peer_state.lock().await.reaches.clone(); + reach::of(node, said.as_ref()) + .is_some_and(|keys| keys.contains(&topology::pair_key(member.local_id()))) +} + +/// x, b and c on one net, all members of one full mesh, but nothing passes between x +/// and c: b reaches both and says so; c learns from b that b reaches x, which c itself +/// doesn't. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn neighbours_tell_whom_they_reach() { + let sim = Sim::with_identity(66); + let cut = crate::transport::sim::LinkParams { + loss: 1.0, + ..Default::default() + }; + sim.net.set_link("x", "c", cut.clone()); + sim.net.set_link("c", "x", cut); + let b = sim.start_on("b", &["sim"], &[], Start::Genesis); + let c = sim.start_on("c", &["sim"], &["b"], Start::Join); + let x = sim.start_on("x", &["sim"], &["b"], Start::Join); + sim.wait_ready(&[&b, &c, &x], SOON).await; + let (b, c, x) = (&b, &c, &x); + sim.wait_until( + "c heard from b that b reaches x, and c doesn't reach x", + SOON, + move || async move { + reaches(b, x) && reaches(b, c) && !reaches(c, x) && heard(c, b, x).await + }, + ) + .await; +} diff --git a/src/simtest/timing.rs b/src/simtest/timing.rs index ea59689..d06e992 100644 --- a/src/simtest/timing.rs +++ b/src/simtest/timing.rs @@ -337,6 +337,7 @@ async fn late_epoch_does_not_flip_a_peers_epoch() { leaving: false, announcing: false, eager: false, + reaches: None, }) }; let a = &a; -- cgit v1.3.1