//! Whom neighbours reach (`swarm::reach`): told in STATUSes, as measured; and the //! writers' trees built on it (`swarm::tree`). use std::time::Duration; use super::Sim; use crate::state::AppState; use crate::swarm::bootstrap::Start; use crate::swarm::{reach, topology, tree}; 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; } /// The same three: in x's tree (`swarm::tree`), c can't take x as its parent (it /// doesn't reach it), so it takes b, which comes before it if c comes later by pair /// key; else c has no parent and gets x's changes from announcements. Either way x's /// only child is b, and nobody but b passes x's changes to c. #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn writer_trees_go_around_links_down() { let sim = Sim::with_identity(67); 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); let writer = topology::pair_key(x.local_id()); let c_after_b = topology::pair_key(c.local_id()) > topology::pair_key(b.local_id()); sim.wait_until( "x's tree: b under x, and c under b if c comes after it", SOON, move || async move { let of_x = tree::children(x, writer).await; let of_b = tree::children(b, writer).await; heard(c, b, x).await && of_x == [b.local_id().to_owned()] && of_b == if c_after_b { vec![c.local_id().to_owned()] } else { Vec::new() } }, ) .await; }