From cca6ed2c62129299c42f1df55e5e456f7714a743 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Mon, 14 Sep 2026 13:45:20 +0000 Subject: [PATCH] test(netmon): pin that peers trading local sources is a medium change The fingerprint maps each peer to the local address it is reached from, rather than holding the set of addresses the host leaves from. The map is what makes a swap visible: two peers exchanging sources leave the host on the same two addresses, while each peer's connected socket is now pinned to the address the other one uses. Nothing in the suite failed if the comparison collapsed to a set of addresses. This test scripts that swap and requires both peers to be reported, each with its own before and after. --- src/node/netmon/tests.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/node/netmon/tests.rs b/src/node/netmon/tests.rs index 30134a3a..e2ae35dd 100644 --- a/src/node/netmon/tests.rs +++ b/src/node/netmon/tests.rs @@ -251,6 +251,43 @@ async fn a_peer_joining_onto_a_settled_path_is_still_not_a_change() { .await; } +#[tokio::test(start_paused = true)] +async fn local_sources_trading_places_between_peers_is_reported_for_both() { + // The reason the fingerprint is a map from peer to source and not a set of + // sources. Two peers swap the local addresses they are reached from, so + // the host still leaves from exactly the same two addresses, yet each + // peer's connected socket is pinned to the source the other peer now + // uses. A set of addresses is unchanged across the swap and would report + // nothing while both sockets are stale. + let a = Some(v4(192, 168, 1, 10)); + let b = Some(v4(10, 40, 0, 7)); + let before = NetFingerprint::for_test(&[(peer(1), a), (peer(2), b)]); + let swapped = NetFingerprint::for_test(&[(peer(1), b), (peer(2), a)]); + let (sampler, _) = scripted(vec![before, swapped]); + let (tx, mut rx) = mpsc::channel(1); + + tokio::spawn(run_detector(tx, cfg(1, 0), sampler, timer_wake(1))); + + let change = expect_change(&mut rx).await; + assert_eq!(change.summary.moved.len(), 2, "{:?}", change.summary.moved); + assert_eq!( + change.summary.moved, + vec![ + PeerSourceMove { + peer: peer(1), + before: a, + after: b, + }, + PeerSourceMove { + peer: peer(2), + before: b, + after: a, + }, + ], + "each peer must be named with its own source before and after the swap" + ); +} + #[tokio::test(start_paused = true)] async fn churn_during_a_handover_does_not_mask_the_handover() { // Both at once: a peer leaves while the medium moves under the peer that