From a382b1793142f1fdeff5b5f0e184f7cb39fe9f20 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sat, 18 Jul 2026 21:24:03 +0000 Subject: [PATCH] node: seed control machines directly in tests, ahead of removing the leg Tests built a free-standing PeerConnection, mutated it, and handed it to Node::add_connection by value. None of those sites survives the removal of PeerConnection, so converting them afterwards would mean one enormous commit that cannot be reviewed honestly. Convert them now, while the struct still exists and the conversion can be validated against a green tree. Adds a cfg(test) Node::seed_handshake_machine plus a HandshakeSeed builder, and rewrites make_completed_connection (now seed_completed_connection) and the twenty inline builders onto it. add_connection keeps its body and loses its test callers. The builder's carrier seeding is a verbatim copy of add_connection's: the two conditional writes for their_index and transport_id, then set_leg, through the same entry().or_insert_with() so an existing leg-less machine keeps its constructor-side fields. Nothing else reaches the carrier -- our_index, source_addr, post-construction started_at and the stored handshake bytes stay leg-only. Seeding more than that would let these tests observe a carrier richer than production's and keep passing even if a later production write-lift were missed. The Noise exchange now runs on the already-seeded leg rather than before the hand-over. That is neutral because the only read of expected_identity is guarded by is_outbound, and no crypto method allocates a session index. Also adds a compile-time check that PeerAction is Clone + Eq, which is what keeps a runtime handle from being smuggled into an action payload. --- src/node/mod.rs | 142 ++++++++++++++++++++++++++ src/node/tests/acl.rs | 21 ++-- src/node/tests/bootstrap.rs | 3 +- src/node/tests/decrypt_failure.rs | 3 +- src/node/tests/establish_chartests.rs | 97 +++++++++++------- src/node/tests/handshake.rs | 125 +++++++++++++++-------- src/node/tests/mod.rs | 54 ++++++---- src/node/tests/routing.rs | 37 +++---- src/node/tests/session.rs | 4 +- src/node/tests/spanning_tree.rs | 24 +++-- src/node/tests/unit.rs | 113 ++++++++++---------- src/peer/machine.rs | 19 ++++ 12 files changed, 436 insertions(+), 206 deletions(-) diff --git a/src/node/mod.rs b/src/node/mod.rs index ea038c9..9799b02 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -2463,6 +2463,77 @@ impl Node { Ok(()) } + /// Test-support: seed a control machine for `seed.link_id` the way + /// [`Node::add_connection`] does, without the caller having to build a + /// free-standing leg first. + /// + /// The carrier seeding below is a verbatim copy of `add_connection`'s: the + /// two conditional writes (`their_index`, `transport_id`) and `set_leg`, + /// built through the same `entry(..).or_insert_with(..)` so an existing + /// leg-less machine keeps its constructor-side fields. Nothing else is + /// written to the carrier — `our_index`, `source_addr`, post-construction + /// `started_at`, and the stored handshake bytes stay leg-only, exactly as + /// they do for a test that goes through `add_connection` today. + /// + /// The duplication is deliberate: keeping the carrier writes visible here + /// is what lets each later step of the leg dissolution revise them at a + /// single reviewable site. The two bodies must stay in sync until + /// `add_connection` itself is removed; any drift surfaces as a test + /// failure while both paths still exist. + #[cfg(test)] + pub(crate) fn seed_handshake_machine(&mut self, seed: HandshakeSeed) -> Result<(), NodeError> { + let link_id = seed.link_id; + + let mut connection = match seed.expected_identity { + Some(identity) => PeerConnection::outbound(link_id, identity, seed.started_at_ms), + None => PeerConnection::inbound(link_id, seed.started_at_ms), + }; + if let Some(id) = seed.transport_id { + connection.set_transport_id(id); + } + if let Some(addr) = seed.source_addr { + connection.set_source_addr(addr); + } + if let Some(index) = seed.our_index { + connection.set_our_index(index); + } + if let Some(index) = seed.their_index { + connection.set_their_index(index); + } + + if self + .peer_machines + .get(&link_id) + .is_some_and(|machine| machine.leg().is_some()) + { + return Err(NodeError::ConnectionAlreadyExists(link_id)); + } + + if self.max_connections() > 0 && self.connection_count() >= self.max_connections() { + return Err(NodeError::MaxConnectionsExceeded { + max: self.max_connections(), + }); + } + + let machine = self.peer_machines.entry(link_id).or_insert_with(|| { + let now = connection.started_at(); + match connection.expected_identity() { + Some(identity) if connection.is_outbound() => { + PeerMachine::new_outbound(link_id, *identity, now) + } + _ => PeerMachine::new_inbound(link_id, now), + } + }); + if let Some(their) = connection.their_index() { + machine.set_conn_their_index(their); + } + if let Some(tid) = connection.transport_id() { + machine.set_conn_transport_id(tid); + } + machine.set_leg(connection); + Ok(()) + } + /// Get a connection by LinkId. pub fn get_connection(&self, link_id: &LinkId) -> Option<&PeerConnection> { self.leg(link_id) @@ -3183,3 +3254,74 @@ impl fmt::Debug for Node { .finish() } } + +/// Test-support seed spec for [`Node::seed_handshake_machine`]. +/// +/// Mirrors the leg constructors plus the setters tests apply to a connection +/// *before* handing it to `add_connection`. Only fields that exist on the leg +/// at add time belong here; crypto is run afterwards through +/// `get_connection_mut`, which `add_connection` never reads. +#[cfg(test)] +#[derive(Debug, Clone)] +pub(crate) struct HandshakeSeed { + link_id: LinkId, + expected_identity: Option, + started_at_ms: u64, + transport_id: Option, + source_addr: Option, + our_index: Option, + their_index: Option, +} + +#[cfg(test)] +impl HandshakeSeed { + /// Outbound leg: we know who we are dialing. + pub(crate) fn outbound( + link_id: LinkId, + expected_identity: PeerIdentity, + started_at_ms: u64, + ) -> Self { + Self { + link_id, + expected_identity: Some(expected_identity), + started_at_ms, + transport_id: None, + source_addr: None, + our_index: None, + their_index: None, + } + } + + /// Inbound leg: identity is unknown until msg1 decrypts. + pub(crate) fn inbound(link_id: LinkId, started_at_ms: u64) -> Self { + Self { + link_id, + expected_identity: None, + started_at_ms, + transport_id: None, + source_addr: None, + our_index: None, + their_index: None, + } + } + + pub(crate) fn with_transport_id(mut self, transport_id: TransportId) -> Self { + self.transport_id = Some(transport_id); + self + } + + pub(crate) fn with_source_addr(mut self, source_addr: TransportAddr) -> Self { + self.source_addr = Some(source_addr); + self + } + + pub(crate) fn with_our_index(mut self, index: crate::utils::index::SessionIndex) -> Self { + self.our_index = Some(index); + self + } + + pub(crate) fn with_their_index(mut self, index: crate::utils::index::SessionIndex) -> Self { + self.their_index = Some(index); + self + } +} diff --git a/src/node/tests/acl.rs b/src/node/tests/acl.rs index b28c77a..1ce5dbc 100644 --- a/src/node/tests/acl.rs +++ b/src/node/tests/acl.rs @@ -84,14 +84,22 @@ async fn test_outbound_msg2_denied_after_acl_reload() { let peer_b_identity = PeerIdentity::from_pubkey_full(node_b.identity().pubkey_full()); let link_id_a = node_a.allocate_link_id(); - let mut conn_a = PeerConnection::outbound(link_id_a, peer_b_identity, 1000); let our_index_a = node_a.index_allocator.allocate().unwrap(); - let noise_msg1 = conn_a - .start_handshake(node_a.identity().keypair(), node_a.startup_epoch(), 1000) + node_a + .seed_handshake_machine( + HandshakeSeed::outbound(link_id_a, peer_b_identity, 1000) + .with_our_index(our_index_a) + .with_transport_id(transport_id) + .with_source_addr(remote_addr.clone()), + ) + .unwrap(); + let keypair_a = node_a.identity().keypair(); + let epoch_a = node_a.startup_epoch(); + let noise_msg1 = node_a + .get_connection_mut(&link_id_a) + .unwrap() + .start_handshake(keypair_a, epoch_a, 1000) .unwrap(); - conn_a.set_our_index(our_index_a); - conn_a.set_transport_id(transport_id); - conn_a.set_source_addr(remote_addr.clone()); let link_a = Link::connectionless( link_id_a, @@ -104,7 +112,6 @@ async fn test_outbound_msg2_denied_after_acl_reload() { node_a .addr_to_link .insert((transport_id, remote_addr.clone()), link_id_a); - node_a.add_connection(conn_a).unwrap(); node_a .pending_outbound .insert((transport_id, our_index_a.as_u32()), link_id_a); diff --git a/src/node/tests/bootstrap.rs b/src/node/tests/bootstrap.rs index 69d3b20..459181b 100644 --- a/src/node/tests/bootstrap.rs +++ b/src/node/tests/bootstrap.rs @@ -127,9 +127,8 @@ async fn test_adopted_traversal_skips_already_connected_peer() { let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, peer_identity) = make_completed_connection(&mut node, link_id, transport_id, 1_000); + let peer_identity = seed_completed_connection(&mut node, link_id, transport_id, 1_000); let peer_node_addr = *peer_identity.node_addr(); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, peer_identity, 2_000) .unwrap(); diff --git a/src/node/tests/decrypt_failure.rs b/src/node/tests/decrypt_failure.rs index 8509339..b590be3 100644 --- a/src/node/tests/decrypt_failure.rs +++ b/src/node/tests/decrypt_failure.rs @@ -28,10 +28,9 @@ fn test_decrypt_failure_threshold_removes_peer() { // Build a fully-promoted active peer with our_index/transport_id set // so peers_by_index is populated by promote_connection. - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 1_000); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 1_000); let node_addr = *identity.node_addr(); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, identity, 2_000).unwrap(); // Sanity: peer is registered and indexed. diff --git a/src/node/tests/establish_chartests.rs b/src/node/tests/establish_chartests.rs index 3acc53e..995e887 100644 --- a/src/node/tests/establish_chartests.rs +++ b/src/node/tests/establish_chartests.rs @@ -192,8 +192,6 @@ async fn chartest_msg1_duplicate_pending_resends_stored_msg2() { // A pending inbound connection with a stored msg2, keyed in addr_to_link, // NOT promoted to an active peer. let link_id = node.allocate_link_id(); - let conn = - PeerConnection::inbound_with_transport(link_id, transport_id, peer_addr.clone(), 1000); let stored_msg2 = vec![0xC1, 0xC2, 0xC3, 0xC4, 0xC5]; let link = Link::connectionless( link_id, @@ -205,7 +203,12 @@ async fn chartest_msg1_duplicate_pending_resends_stored_msg2() { node.links.insert(link_id, link); node.addr_to_link .insert((transport_id, peer_addr.clone()), link_id); - node.add_connection(conn).unwrap(); + node.seed_handshake_machine( + HandshakeSeed::inbound(link_id, 1000) + .with_transport_id(transport_id) + .with_source_addr(peer_addr.clone()), + ) + .unwrap(); // The stored msg2 lives on the control machine's carrier (the resend source // for a duplicate msg1 while pending), mirroring the inbound establish path. node.peer_machines @@ -327,15 +330,21 @@ async fn chartest_msg1_inbound_promote_defers_pending_outbound_to_same_identity( // different source address. let out_link = node.allocate_link_id(); let out_addr = TransportAddr::from_string("10.0.0.9:2121"); - let mut out_conn = PeerConnection::outbound(out_link, sender_pid, 1000); - let our_keypair = node.identity().keypair(); - let _ = out_conn - .start_handshake(our_keypair, node.startup_epoch(), 1000) - .unwrap(); let out_index = node.index_allocator.allocate().unwrap(); - out_conn.set_our_index(out_index); - out_conn.set_transport_id(transport_id); - out_conn.set_source_addr(out_addr.clone()); + node.seed_handshake_machine( + HandshakeSeed::outbound(out_link, sender_pid, 1000) + .with_our_index(out_index) + .with_transport_id(transport_id) + .with_source_addr(out_addr.clone()), + ) + .unwrap(); + let our_keypair = node.identity().keypair(); + let startup_epoch = node.startup_epoch(); + let _ = node + .get_connection_mut(&out_link) + .unwrap() + .start_handshake(our_keypair, startup_epoch, 1000) + .unwrap(); let out_l = Link::connectionless( out_link, transport_id, @@ -346,7 +355,6 @@ async fn chartest_msg1_inbound_promote_defers_pending_outbound_to_same_identity( node.links.insert(out_link, out_l); node.addr_to_link .insert((transport_id, out_addr.clone()), out_link); - node.add_connection(out_conn).unwrap(); node.pending_outbound .insert((transport_id, out_index.as_u32()), out_link); assert_eq!(node.peer_count(), 0); @@ -402,16 +410,21 @@ async fn chartest_msg1_at_cap_with_pending_outbound_bypasses_early_gate() { // `has_pending_outbound_to_peer`, which turns off the early silent-drop. let out_link = node.allocate_link_id(); let out_addr = TransportAddr::from_string("10.0.0.9:2121"); - let mut out_conn = PeerConnection::outbound(out_link, sender_pid, 1000); - let our_keypair = node.identity().keypair(); - let _ = out_conn - .start_handshake(our_keypair, node.startup_epoch(), 1000) - .unwrap(); let out_index = node.index_allocator.allocate().unwrap(); - out_conn.set_our_index(out_index); - out_conn.set_transport_id(transport_id); - out_conn.set_source_addr(out_addr.clone()); - node.add_connection(out_conn).unwrap(); + node.seed_handshake_machine( + HandshakeSeed::outbound(out_link, sender_pid, 1000) + .with_our_index(out_index) + .with_transport_id(transport_id) + .with_source_addr(out_addr.clone()), + ) + .unwrap(); + let our_keypair = node.identity().keypair(); + let startup_epoch = node.startup_epoch(); + let _ = node + .get_connection_mut(&out_link) + .unwrap() + .start_handshake(our_keypair, startup_epoch, 1000) + .unwrap(); node.pending_outbound .insert((transport_id, out_index.as_u32()), out_link); @@ -499,14 +512,22 @@ async fn chartest_cross_connection_tiebreak_winner_and_loser() { // A initiates to B. let link_a_out = node_a.allocate_link_id(); - let mut conn_a = PeerConnection::outbound(link_a_out, peer_b_identity, 1000); let out_index_a = node_a.index_allocator.allocate().unwrap(); - let noise_msg1_a = conn_a - .start_handshake(node_a.identity().keypair(), node_a.startup_epoch(), 1000) + node_a + .seed_handshake_machine( + HandshakeSeed::outbound(link_a_out, peer_b_identity, 1000) + .with_our_index(out_index_a) + .with_transport_id(transport_id_a) + .with_source_addr(remote_addr_b.clone()), + ) + .unwrap(); + let keypair_a = node_a.identity().keypair(); + let epoch_a = node_a.startup_epoch(); + let noise_msg1_a = node_a + .get_connection_mut(&link_a_out) + .unwrap() + .start_handshake(keypair_a, epoch_a, 1000) .unwrap(); - conn_a.set_our_index(out_index_a); - conn_a.set_transport_id(transport_id_a); - conn_a.set_source_addr(remote_addr_b.clone()); let wire_msg1_a = build_msg1(out_index_a, &noise_msg1_a); node_a.links.insert( link_a_out, @@ -521,21 +542,28 @@ async fn chartest_cross_connection_tiebreak_winner_and_loser() { node_a .addr_to_link .insert((transport_id_a, remote_addr_b.clone()), link_a_out); - node_a.add_connection(conn_a).unwrap(); node_a .pending_outbound .insert((transport_id_a, out_index_a.as_u32()), link_a_out); // B initiates to A. let link_b_out = node_b.allocate_link_id(); - let mut conn_b = PeerConnection::outbound(link_b_out, peer_a_identity, 1000); let out_index_b = node_b.index_allocator.allocate().unwrap(); - let noise_msg1_b = conn_b - .start_handshake(node_b.identity().keypair(), node_b.startup_epoch(), 1000) + node_b + .seed_handshake_machine( + HandshakeSeed::outbound(link_b_out, peer_a_identity, 1000) + .with_our_index(out_index_b) + .with_transport_id(transport_id_b) + .with_source_addr(remote_addr_a.clone()), + ) + .unwrap(); + let keypair_b = node_b.identity().keypair(); + let epoch_b = node_b.startup_epoch(); + let noise_msg1_b = node_b + .get_connection_mut(&link_b_out) + .unwrap() + .start_handshake(keypair_b, epoch_b, 1000) .unwrap(); - conn_b.set_our_index(out_index_b); - conn_b.set_transport_id(transport_id_b); - conn_b.set_source_addr(remote_addr_a.clone()); let wire_msg1_b = build_msg1(out_index_b, &noise_msg1_b); node_b.links.insert( link_b_out, @@ -550,7 +578,6 @@ async fn chartest_cross_connection_tiebreak_winner_and_loser() { node_b .addr_to_link .insert((transport_id_b, remote_addr_a.clone()), link_b_out); - node_b.add_connection(conn_b).unwrap(); node_b .pending_outbound .insert((transport_id_b, out_index_b.as_u32()), link_b_out); diff --git a/src/node/tests/handshake.rs b/src/node/tests/handshake.rs index e5438bc..da5e087 100644 --- a/src/node/tests/handshake.rs +++ b/src/node/tests/handshake.rs @@ -53,19 +53,27 @@ async fn test_two_node_handshake_udp() { let peer_b_node_addr = *peer_b_identity.node_addr(); let link_id_a = node_a.allocate_link_id(); - let mut conn_a = PeerConnection::outbound(link_id_a, peer_b_identity, 1000); // Allocate session index for A's outbound let our_index_a = node_a.index_allocator.allocate().unwrap(); + node_a + .seed_handshake_machine( + HandshakeSeed::outbound(link_id_a, peer_b_identity, 1000) + .with_our_index(our_index_a) + .with_transport_id(transport_id_a) + .with_source_addr(remote_addr_b.clone()), + ) + .unwrap(); + // Start handshake (generates Noise IK msg1) let our_keypair_a = node_a.identity().keypair(); - let noise_msg1 = conn_a - .start_handshake(our_keypair_a, node_a.startup_epoch(), 1000) + let startup_epoch_a = node_a.startup_epoch(); + let noise_msg1 = node_a + .get_connection_mut(&link_id_a) + .unwrap() + .start_handshake(our_keypair_a, startup_epoch_a, 1000) .unwrap(); - conn_a.set_our_index(our_index_a); - conn_a.set_transport_id(transport_id_a); - conn_a.set_source_addr(remote_addr_b.clone()); // Build wire msg1 and track in node state let wire_msg1 = build_msg1(our_index_a, &noise_msg1); @@ -78,7 +86,6 @@ async fn test_two_node_handshake_udp() { Duration::from_millis(100), ); node_a.links.insert(link_id_a, link_a); - node_a.add_connection(conn_a).unwrap(); node_a .pending_outbound .insert((transport_id_a, our_index_a.as_u32()), link_id_a); @@ -294,16 +301,23 @@ async fn test_run_rx_loop_handshake() { let peer_b_node_addr = *peer_b_identity.node_addr(); let link_id_a = node_a.allocate_link_id(); - let mut conn_a = PeerConnection::outbound(link_id_a, peer_b_identity, 1000); let our_index_a = node_a.index_allocator.allocate().unwrap(); - let our_keypair_a = node_a.identity().keypair(); - let noise_msg1 = conn_a - .start_handshake(our_keypair_a, node_a.startup_epoch(), 1000) + node_a + .seed_handshake_machine( + HandshakeSeed::outbound(link_id_a, peer_b_identity, 1000) + .with_our_index(our_index_a) + .with_transport_id(transport_id_a) + .with_source_addr(remote_addr_b.clone()), + ) + .unwrap(); + let our_keypair_a = node_a.identity().keypair(); + let startup_epoch_a = node_a.startup_epoch(); + let noise_msg1 = node_a + .get_connection_mut(&link_id_a) + .unwrap() + .start_handshake(our_keypair_a, startup_epoch_a, 1000) .unwrap(); - conn_a.set_our_index(our_index_a); - conn_a.set_transport_id(transport_id_a); - conn_a.set_source_addr(remote_addr_b.clone()); let wire_msg1 = build_msg1(our_index_a, &noise_msg1); @@ -315,7 +329,6 @@ async fn test_run_rx_loop_handshake() { Duration::from_millis(100), ); node_a.links.insert(link_id_a, link_a); - node_a.add_connection(conn_a).unwrap(); node_a .pending_outbound .insert((transport_id_a, our_index_a.as_u32()), link_id_a); @@ -483,15 +496,22 @@ async fn test_cross_connection_both_initiate() { // Node A initiates to Node B let link_id_a_out = node_a.allocate_link_id(); - let mut conn_a = PeerConnection::outbound(link_id_a_out, peer_b_identity, 1000); let our_index_a = node_a.index_allocator.allocate().unwrap(); - let our_keypair_a = node_a.identity().keypair(); - let noise_msg1_a = conn_a - .start_handshake(our_keypair_a, node_a.startup_epoch(), 1000) + node_a + .seed_handshake_machine( + HandshakeSeed::outbound(link_id_a_out, peer_b_identity, 1000) + .with_our_index(our_index_a) + .with_transport_id(transport_id_a) + .with_source_addr(remote_addr_b.clone()), + ) + .unwrap(); + let our_keypair_a = node_a.identity().keypair(); + let startup_epoch_a = node_a.startup_epoch(); + let noise_msg1_a = node_a + .get_connection_mut(&link_id_a_out) + .unwrap() + .start_handshake(our_keypair_a, startup_epoch_a, 1000) .unwrap(); - conn_a.set_our_index(our_index_a); - conn_a.set_transport_id(transport_id_a); - conn_a.set_source_addr(remote_addr_b.clone()); let wire_msg1_a = build_msg1(our_index_a, &noise_msg1_a); @@ -506,22 +526,28 @@ async fn test_cross_connection_both_initiate() { node_a .addr_to_link .insert((transport_id_a, remote_addr_b.clone()), link_id_a_out); - node_a.add_connection(conn_a).unwrap(); node_a .pending_outbound .insert((transport_id_a, our_index_a.as_u32()), link_id_a_out); // Node B initiates to Node A let link_id_b_out = node_b.allocate_link_id(); - let mut conn_b = PeerConnection::outbound(link_id_b_out, peer_a_identity, 1000); let our_index_b = node_b.index_allocator.allocate().unwrap(); - let our_keypair_b = node_b.identity().keypair(); - let noise_msg1_b = conn_b - .start_handshake(our_keypair_b, node_b.startup_epoch(), 1000) + node_b + .seed_handshake_machine( + HandshakeSeed::outbound(link_id_b_out, peer_a_identity, 1000) + .with_our_index(our_index_b) + .with_transport_id(transport_id_b) + .with_source_addr(remote_addr_a.clone()), + ) + .unwrap(); + let our_keypair_b = node_b.identity().keypair(); + let startup_epoch_b = node_b.startup_epoch(); + let noise_msg1_b = node_b + .get_connection_mut(&link_id_b_out) + .unwrap() + .start_handshake(our_keypair_b, startup_epoch_b, 1000) .unwrap(); - conn_b.set_our_index(our_index_b); - conn_b.set_transport_id(transport_id_b); - conn_b.set_source_addr(remote_addr_a.clone()); let wire_msg1_b = build_msg1(our_index_b, &noise_msg1_b); @@ -536,7 +562,6 @@ async fn test_cross_connection_both_initiate() { node_b .addr_to_link .insert((transport_id_b, remote_addr_a.clone()), link_id_b_out); - node_b.add_connection(conn_b).unwrap(); node_b .pending_outbound .insert((transport_id_b, our_index_b.as_u32()), link_id_b_out); @@ -662,17 +687,23 @@ async fn test_stale_connection_cleanup() { // Create outbound connection with a timestamp far in the past let past_time_ms = 1000; // A very early timestamp let link_id = node.allocate_link_id(); - let mut conn = PeerConnection::outbound(link_id, peer_identity, past_time_ms); // Allocate session index and set transport info let our_index = node.index_allocator.allocate().unwrap(); + node.seed_handshake_machine( + HandshakeSeed::outbound(link_id, peer_identity, past_time_ms) + .with_our_index(our_index) + .with_transport_id(transport_id) + .with_source_addr(remote_addr.clone()), + ) + .unwrap(); let our_keypair = node.identity().keypair(); - let _noise_msg1 = conn - .start_handshake(our_keypair, node.startup_epoch(), past_time_ms) + let startup_epoch = node.startup_epoch(); + let _noise_msg1 = node + .get_connection_mut(&link_id) + .unwrap() + .start_handshake(our_keypair, startup_epoch, past_time_ms) .unwrap(); - conn.set_our_index(our_index); - conn.set_transport_id(transport_id); - conn.set_source_addr(remote_addr.clone()); // Set up all the state that initiate_peer_connection would create let link = Link::connectionless( @@ -685,7 +716,6 @@ async fn test_stale_connection_cleanup() { node.links.insert(link_id, link); node.addr_to_link .insert((transport_id, remote_addr.clone()), link_id); - node.add_connection(conn).unwrap(); node.pending_outbound .insert((transport_id, our_index.as_u32()), link_id); @@ -741,16 +771,22 @@ async fn test_failed_connection_cleanup() { .map(|d| d.as_millis() as u64) .unwrap_or(0); let link_id = node.allocate_link_id(); - let mut conn = PeerConnection::outbound(link_id, peer_identity, now_ms); let our_index = node.index_allocator.allocate().unwrap(); + node.seed_handshake_machine( + HandshakeSeed::outbound(link_id, peer_identity, now_ms) + .with_our_index(our_index) + .with_transport_id(transport_id) + .with_source_addr(remote_addr.clone()), + ) + .unwrap(); let our_keypair = node.identity().keypair(); - let _noise_msg1 = conn - .start_handshake(our_keypair, node.startup_epoch(), now_ms) + let startup_epoch = node.startup_epoch(); + let _noise_msg1 = node + .get_connection_mut(&link_id) + .unwrap() + .start_handshake(our_keypair, startup_epoch, now_ms) .unwrap(); - conn.set_our_index(our_index); - conn.set_transport_id(transport_id); - conn.set_source_addr(remote_addr.clone()); let link = Link::connectionless( link_id, @@ -762,7 +798,6 @@ async fn test_failed_connection_cleanup() { node.links.insert(link_id, link); node.addr_to_link .insert((transport_id, remote_addr.clone()), link_id); - node.add_connection(conn).unwrap(); node.pending_outbound .insert((transport_id, our_index.as_u32()), link_id); diff --git a/src/node/tests/mod.rs b/src/node/tests/mod.rs index 8754f50..57d2500 100644 --- a/src/node/tests/mod.rs +++ b/src/node/tests/mod.rs @@ -84,27 +84,49 @@ pub(super) fn make_peer_identity() -> PeerIdentity { PeerIdentity::from_pubkey(identity.pubkey()) } -/// Create a PeerConnection with a completed Noise IK handshake. +/// Seed a control machine whose leg carries a completed Noise IK handshake. /// -/// Returns (connection, peer_identity) where the connection is outbound, -/// in Complete state, with session, indices, and transport info set. -pub(super) fn make_completed_connection( +/// Returns the peer identity. The leg is outbound, in Complete state, with +/// session, indices, and transport info set, and is installed on the node +/// through [`Node::seed_handshake_machine`] — the test-surface twin of +/// `Node::add_connection`. +/// +/// The Noise exchange runs on the already-seeded leg, where it used to run +/// before the leg was handed over. That reordering is neutral, but not +/// because the handshake leaves the seeded fields alone — +/// `receive_handshake_init` does write `expected_identity`. It is neutral +/// because the only read of `expected_identity` is guarded by `is_outbound`: +/// an inbound leg takes the `new_inbound` arm whether or not the identity has +/// been learned, and an outbound leg never runs that method. The remaining +/// reads (`link_id`, `started_at`, `is_outbound`, `their_index`, +/// `transport_id`) are genuinely untouched by the handshake. +pub(super) fn seed_completed_connection( node: &mut Node, link_id: LinkId, transport_id: TransportId, current_time_ms: u64, -) -> (PeerConnection, PeerIdentity) { +) -> PeerIdentity { let peer_identity_full = Identity::generate(); // Must use from_pubkey_full to preserve parity for ECDH let peer_identity = PeerIdentity::from_pubkey_full(peer_identity_full.pubkey_full()); - // Create outbound connection - let mut conn = PeerConnection::outbound(link_id, peer_identity, current_time_ms); + let our_index = node.index_allocator.allocate().unwrap(); + node.seed_handshake_machine( + HandshakeSeed::outbound(link_id, peer_identity, current_time_ms) + .with_our_index(our_index) + .with_their_index(SessionIndex::new(42)) + .with_transport_id(transport_id) + .with_source_addr(TransportAddr::from_string("127.0.0.1:5000")), + ) + .unwrap(); // Run initiator side of handshake let our_keypair = node.identity().keypair(); - let msg1 = conn - .start_handshake(our_keypair, node.startup_epoch(), current_time_ms) + let startup_epoch = node.startup_epoch(); + let msg1 = node + .get_connection_mut(&link_id) + .unwrap() + .start_handshake(our_keypair, startup_epoch, current_time_ms) .unwrap(); // Run responder side to generate msg2 @@ -117,14 +139,10 @@ pub(super) fn make_completed_connection( .unwrap(); // Complete initiator handshake - conn.complete_handshake(&msg2, current_time_ms).unwrap(); + node.get_connection_mut(&link_id) + .unwrap() + .complete_handshake(&msg2, current_time_ms) + .unwrap(); - // Set indices and transport info - let our_index = node.index_allocator.allocate().unwrap(); - conn.set_our_index(our_index); - conn.set_their_index(SessionIndex::new(42)); - conn.set_transport_id(transport_id); - conn.set_source_addr(TransportAddr::from_string("127.0.0.1:5000")); - - (conn, peer_identity) + peer_identity } diff --git a/src/node/tests/routing.rs b/src/node/tests/routing.rs index 5123581..cd6b1c2 100644 --- a/src/node/tests/routing.rs +++ b/src/node/tests/routing.rs @@ -29,9 +29,8 @@ fn test_routing_direct_peer() { let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); let peer_addr = *identity.node_addr(); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, identity, 2000).unwrap(); let result = node.find_next_hop(&peer_addr); @@ -58,15 +57,13 @@ fn test_routing_bloom_filter_hit() { // Create two peers let link_id1 = LinkId::new(1); - let (conn1, id1) = make_completed_connection(&mut node, link_id1, transport_id, 1000); + let id1 = seed_completed_connection(&mut node, link_id1, transport_id, 1000); let peer1_addr = *id1.node_addr(); - node.add_connection(conn1).unwrap(); node.promote_connection(link_id1, id1, 2000).unwrap(); let link_id2 = LinkId::new(2); - let (conn2, id2) = make_completed_connection(&mut node, link_id2, transport_id, 1000); + let id2 = seed_completed_connection(&mut node, link_id2, transport_id, 1000); let peer2_addr = *id2.node_addr(); - node.add_connection(conn2).unwrap(); node.promote_connection(link_id2, id2, 2000).unwrap(); // Set up tree: we are root, both peers are our children @@ -115,10 +112,9 @@ fn test_routing_bloom_filter_multiple_hits_tiebreak() { let mut peer_addrs = Vec::new(); for i in 1..=3 { let link_id = LinkId::new(i); - let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let id = seed_completed_connection(&mut node, link_id, transport_id, 1000); let addr = *id.node_addr(); peer_addrs.push(addr); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, id, 2000).unwrap(); } @@ -166,9 +162,8 @@ fn test_routing_tree_fallback() { // Create a peer let link_id = LinkId::new(1); - let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let id = seed_completed_connection(&mut node, link_id, transport_id, 1000); let peer_addr = *id.node_addr(); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, id, 2000).unwrap(); // Set up tree state through the public API. @@ -220,9 +215,8 @@ fn test_routing_bloom_hit_not_closer_falls_through_to_tree() { // tree_peer: child of self, on the path to dest (greedy tree pick). let tree_link = LinkId::new(1); - let (tree_conn, tree_id) = make_completed_connection(&mut node, tree_link, transport_id, 1000); + let tree_id = seed_completed_connection(&mut node, tree_link, transport_id, 1000); let tree_peer_addr = *tree_id.node_addr(); - node.add_connection(tree_conn).unwrap(); node.promote_connection(tree_link, tree_id, 2000).unwrap(); // bloom_peer: also a child of self, but with a stale/false-positive @@ -230,10 +224,8 @@ fn test_routing_bloom_hit_not_closer_falls_through_to_tree() { // ours, so the self-distance check in select_best_candidate excludes // it — leaving zero viable bloom candidates. let bloom_link = LinkId::new(2); - let (bloom_conn, bloom_id) = - make_completed_connection(&mut node, bloom_link, transport_id, 1000); + let bloom_id = seed_completed_connection(&mut node, bloom_link, transport_id, 1000); let bloom_peer_addr = *bloom_id.node_addr(); - node.add_connection(bloom_conn).unwrap(); node.promote_connection(bloom_link, bloom_id, 2000).unwrap(); // Tree topology (we are root): @@ -300,8 +292,7 @@ fn test_routing_tree_no_coords_in_cache() { // Create a peer let link_id = LinkId::new(1); - let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000); - node.add_connection(conn).unwrap(); + let id = seed_completed_connection(&mut node, link_id, transport_id, 1000); node.promote_connection(link_id, id, 2000).unwrap(); // Destination not in bloom filters and not in coord cache @@ -319,9 +310,8 @@ fn test_routing_refreshes_coord_cache_ttl() { // Create a peer let link_id = LinkId::new(1); - let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let id = seed_completed_connection(&mut node, link_id, transport_id, 1000); let peer_addr = *id.node_addr(); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, id, 2000).unwrap(); // Set up tree coordinates @@ -364,15 +354,13 @@ fn test_routing_bloom_hit_without_coords_returns_none() { // Create two peers let link_id1 = LinkId::new(1); - let (conn1, id1) = make_completed_connection(&mut node, link_id1, transport_id, 1000); + let id1 = seed_completed_connection(&mut node, link_id1, transport_id, 1000); let peer1_addr = *id1.node_addr(); - node.add_connection(conn1).unwrap(); node.promote_connection(link_id1, id1, 2000).unwrap(); let link_id2 = LinkId::new(2); - let (conn2, id2) = make_completed_connection(&mut node, link_id2, transport_id, 1000); + let id2 = seed_completed_connection(&mut node, link_id2, transport_id, 1000); let peer2_addr = *id2.node_addr(); - node.add_connection(conn2).unwrap(); node.promote_connection(link_id2, id2, 2000).unwrap(); let dest = make_node_addr(99); @@ -404,9 +392,8 @@ fn test_routing_discovery_coord_cache() { // Create a peer let link_id = LinkId::new(1); - let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let id = seed_completed_connection(&mut node, link_id, transport_id, 1000); let peer_addr = *id.node_addr(); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, id, 2000).unwrap(); // Set up tree: we are root, peer is our child diff --git a/src/node/tests/session.rs b/src/node/tests/session.rs index 8d50d1f..4ad7c65 100644 --- a/src/node/tests/session.rs +++ b/src/node/tests/session.rs @@ -1008,9 +1008,7 @@ fn test_identity_cache_populated_on_promote() { let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, peer_identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); - - node.add_connection(conn).unwrap(); + let peer_identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); // Promote let result = node diff --git a/src/node/tests/spanning_tree.rs b/src/node/tests/spanning_tree.rs index 12b323e..cb6e8eb 100644 --- a/src/node/tests/spanning_tree.rs +++ b/src/node/tests/spanning_tree.rs @@ -116,16 +116,25 @@ pub(super) async fn initiate_handshake(nodes: &mut [TestNode], i: usize, j: usiz let transport_id = initiator.transport_id; let link_id = initiator.node.allocate_link_id(); - let mut conn = PeerConnection::outbound(link_id, peer_identity, 1000); let our_index = initiator.node.index_allocator.allocate().unwrap(); - let our_keypair = initiator.node.identity().keypair(); - let noise_msg1 = conn - .start_handshake(our_keypair, initiator.node.startup_epoch(), 1000) + initiator + .node + .seed_handshake_machine( + HandshakeSeed::outbound(link_id, peer_identity, 1000) + .with_our_index(our_index) + .with_transport_id(transport_id) + .with_source_addr(responder_addr.clone()), + ) + .unwrap(); + let our_keypair = initiator.node.identity().keypair(); + let startup_epoch = initiator.node.startup_epoch(); + let noise_msg1 = initiator + .node + .get_connection_mut(&link_id) + .unwrap() + .start_handshake(our_keypair, startup_epoch, 1000) .unwrap(); - conn.set_our_index(our_index); - conn.set_transport_id(transport_id); - conn.set_source_addr(responder_addr.clone()); let wire_msg1 = build_msg1(our_index, &noise_msg1); @@ -141,7 +150,6 @@ pub(super) async fn initiate_handshake(nodes: &mut [TestNode], i: usize, j: usiz .node .addr_to_link .insert((transport_id, responder_addr.clone()), link_id); - initiator.node.add_connection(conn).unwrap(); initiator .node .pending_outbound diff --git a/src/node/tests/unit.rs b/src/node/tests/unit.rs index 13731c3..3185ba8 100644 --- a/src/node/tests/unit.rs +++ b/src/node/tests/unit.rs @@ -337,9 +337,9 @@ fn test_node_connection_management() { let identity = make_peer_identity(); let link_id = LinkId::new(1); - let conn = PeerConnection::outbound(link_id, identity, 1000); + node.seed_handshake_machine(HandshakeSeed::outbound(link_id, identity, 1000)) + .unwrap(); - node.add_connection(conn).unwrap(); assert_eq!(node.connection_count(), 1); assert!(node.get_connection(&link_id).is_some()); @@ -354,11 +354,10 @@ fn test_node_connection_duplicate() { let identity = make_peer_identity(); let link_id = LinkId::new(1); - let conn1 = PeerConnection::outbound(link_id, identity, 1000); - let conn2 = PeerConnection::outbound(link_id, identity, 2000); + node.seed_handshake_machine(HandshakeSeed::outbound(link_id, identity, 1000)) + .unwrap(); - node.add_connection(conn1).unwrap(); - let result = node.add_connection(conn2); + let result = node.seed_handshake_machine(HandshakeSeed::outbound(link_id, identity, 2000)); assert!(matches!(result, Err(NodeError::ConnectionAlreadyExists(_)))); } @@ -370,9 +369,8 @@ fn test_peer_maps_coherent_after_add_connection() { let identity = make_peer_identity(); let link_id = LinkId::new(1); - let conn = PeerConnection::outbound(link_id, identity, 1000); - - node.add_connection(conn).unwrap(); + node.seed_handshake_machine(HandshakeSeed::outbound(link_id, identity, 1000)) + .unwrap(); assert!( node.peer_machines.contains_key(&link_id), @@ -388,9 +386,8 @@ fn test_peer_maps_coherent_through_establish() { let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); - node.add_connection(conn).unwrap(); node.debug_assert_peer_maps_coherent(); let result = node.promote_connection(link_id, identity, 2000).unwrap(); @@ -427,10 +424,9 @@ fn test_node_promote_connection() { let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); let node_addr = *identity.node_addr(); - node.add_connection(conn).unwrap(); assert_eq!(node.connection_count(), 1); assert_eq!(node.peer_count(), 0); @@ -467,10 +463,9 @@ fn test_node_cross_connection_resolution() { // First connection and promotion (becomes active peer) let link_id1 = LinkId::new(1); - let (conn1, identity) = make_completed_connection(&mut node, link_id1, transport_id, 1000); + let identity = seed_completed_connection(&mut node, link_id1, transport_id, 1000); let node_addr = *identity.node_addr(); - node.add_connection(conn1).unwrap(); node.promote_connection(link_id1, identity, 1500).unwrap(); assert_eq!(node.peer_count(), 1); @@ -500,8 +495,7 @@ fn test_node_peer_limit() { // Add two peers via promotion for i in 0..2 { let link_id = LinkId::new(i as u64 + 1); - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); - node.add_connection(conn).unwrap(); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); node.promote_connection(link_id, identity, 2000).unwrap(); } @@ -509,8 +503,7 @@ fn test_node_peer_limit() { // Third should fail let link_id = LinkId::new(3); - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 3000); - node.add_connection(conn).unwrap(); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 3000); let result = node.promote_connection(link_id, identity, 4000); assert!(matches!(result, Err(NodeError::MaxPeersExceeded { .. }))); @@ -558,22 +551,19 @@ fn test_node_sendable_peers() { // Add a healthy peer let link_id1 = LinkId::new(1); - let (conn1, identity1) = make_completed_connection(&mut node, link_id1, transport_id, 1000); + let identity1 = seed_completed_connection(&mut node, link_id1, transport_id, 1000); let node_addr1 = *identity1.node_addr(); - node.add_connection(conn1).unwrap(); node.promote_connection(link_id1, identity1, 2000).unwrap(); // Add another peer and mark it stale (still sendable) let link_id2 = LinkId::new(2); - let (conn2, identity2) = make_completed_connection(&mut node, link_id2, transport_id, 1000); - node.add_connection(conn2).unwrap(); + let identity2 = seed_completed_connection(&mut node, link_id2, transport_id, 1000); node.promote_connection(link_id2, identity2, 2000).unwrap(); // Add a third peer and mark it disconnected (not sendable) let link_id3 = LinkId::new(3); - let (conn3, identity3) = make_completed_connection(&mut node, link_id3, transport_id, 1000); + let identity3 = seed_completed_connection(&mut node, link_id3, transport_id, 1000); let node_addr3 = *identity3.node_addr(); - node.add_connection(conn3).unwrap(); node.promote_connection(link_id3, identity3, 2000).unwrap(); node.get_peer_mut(&node_addr3).unwrap().mark_disconnected(); @@ -708,20 +698,24 @@ fn test_promote_cleans_up_pending_outbound_to_same_peer() { // This simulates A having sent msg1 to B before B was running. let pending_link_id = LinkId::new(1); let pending_time_ms = 1000; - let mut pending_conn = - PeerConnection::outbound(pending_link_id, peer_b_identity, pending_time_ms); + let pending_index = node.index_allocator.allocate().unwrap(); + let pending_addr = TransportAddr::from_string("10.0.0.2:2121"); + node.seed_handshake_machine( + HandshakeSeed::outbound(pending_link_id, peer_b_identity, pending_time_ms) + .with_our_index(pending_index) + .with_transport_id(transport_id) + .with_source_addr(pending_addr.clone()), + ) + .unwrap(); let our_keypair = node.identity().keypair(); - let _msg1 = pending_conn - .start_handshake(our_keypair, node.startup_epoch(), pending_time_ms) + let startup_epoch = node.startup_epoch(); + let _msg1 = node + .get_connection_mut(&pending_link_id) + .unwrap() + .start_handshake(our_keypair, startup_epoch, pending_time_ms) .unwrap(); - let pending_index = node.index_allocator.allocate().unwrap(); - pending_conn.set_our_index(pending_index); - pending_conn.set_transport_id(transport_id); - let pending_addr = TransportAddr::from_string("10.0.0.2:2121"); - pending_conn.set_source_addr(pending_addr.clone()); - let pending_link = Link::connectionless( pending_link_id, transport_id, @@ -732,7 +726,6 @@ fn test_promote_cleans_up_pending_outbound_to_same_peer() { node.links.insert(pending_link_id, pending_link); node.addr_to_link .insert((transport_id, pending_addr.clone()), pending_link_id); - node.add_connection(pending_conn).unwrap(); node.pending_outbound .insert((transport_id, pending_index.as_u32()), pending_link_id); @@ -747,12 +740,22 @@ fn test_promote_cleans_up_pending_outbound_to_same_peer() { let completing_link_id = LinkId::new(2); let completing_time_ms = 2000; - let mut completing_conn = - PeerConnection::outbound(completing_link_id, peer_b_identity, completing_time_ms); + let completing_index = node.index_allocator.allocate().unwrap(); + node.seed_handshake_machine( + HandshakeSeed::outbound(completing_link_id, peer_b_identity, completing_time_ms) + .with_our_index(completing_index) + .with_their_index(SessionIndex::new(99)) + .with_transport_id(transport_id) + .with_source_addr(TransportAddr::from_string("10.0.0.2:4001")), + ) + .unwrap(); let our_keypair = node.identity().keypair(); - let msg1 = completing_conn - .start_handshake(our_keypair, node.startup_epoch(), completing_time_ms) + let startup_epoch = node.startup_epoch(); + let msg1 = node + .get_connection_mut(&completing_link_id) + .unwrap() + .start_handshake(our_keypair, startup_epoch, completing_time_ms) .unwrap(); // B responds @@ -764,18 +767,11 @@ fn test_promote_cleans_up_pending_outbound_to_same_peer() { .receive_handshake_init(peer_keypair, resp_epoch, &msg1, completing_time_ms) .unwrap(); - completing_conn + node.get_connection_mut(&completing_link_id) + .unwrap() .complete_handshake(&msg2, completing_time_ms) .unwrap(); - let completing_index = node.index_allocator.allocate().unwrap(); - completing_conn.set_our_index(completing_index); - completing_conn.set_their_index(SessionIndex::new(99)); - completing_conn.set_transport_id(transport_id); - completing_conn.set_source_addr(TransportAddr::from_string("10.0.0.2:4001")); - - node.add_connection(completing_conn).unwrap(); - // Now 2 connections, 1 link (pending has link, completing doesn't yet need one for this test) assert_eq!(node.connection_count(), 2); assert_eq!(node.index_allocator.count(), 2); @@ -1039,9 +1035,8 @@ fn test_schedule_retry_skips_connected_peer() { // Promote a peer so it's in the peers map let link_id = LinkId::new(1); - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); let node_addr = *identity.node_addr(); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, identity, 2000).unwrap(); assert_eq!(node.peer_count(), 1); @@ -1058,10 +1053,9 @@ async fn test_try_peer_addresses_skips_connected_peer() { let mut node = make_node(); let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, peer_identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let peer_identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); let peer_config = crate::config::PeerConfig::new(peer_identity.npub(), "udp", "127.0.0.1:9"); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, peer_identity, 2000) .unwrap(); let link_count = node.link_count(); @@ -1088,8 +1082,8 @@ async fn test_try_peer_addresses_skips_connecting_peer() { let mut node = make_node(); let peer_identity = make_peer_identity(); let peer_config = crate::config::PeerConfig::new(peer_identity.npub(), "udp", "127.0.0.1:9"); - let pending = PeerConnection::outbound(LinkId::new(1), peer_identity, 1000); - node.add_connection(pending).unwrap(); + node.seed_handshake_machine(HandshakeSeed::outbound(LinkId::new(1), peer_identity, 1000)) + .unwrap(); node.try_peer_addresses(&peer_config, peer_identity, true) .await @@ -1270,8 +1264,7 @@ async fn test_nostr_traversal_failure_skips_connected_peer() { let mut node = make_node(); let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, peer_identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); - node.add_connection(conn).unwrap(); + let peer_identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); node.promote_connection(link_id, peer_identity, 2000) .unwrap(); @@ -1304,8 +1297,7 @@ async fn test_nostr_traversal_established_skips_connected_peer() { let mut node = make_node(); let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, peer_identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); - node.add_connection(conn).unwrap(); + let peer_identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); node.promote_connection(link_id, peer_identity, 2000) .unwrap(); let link_count = node.link_count(); @@ -1525,7 +1517,7 @@ fn test_promote_clears_retry_pending() { let transport_id = TransportId::new(1); let link_id = LinkId::new(1); - let (conn, identity) = make_completed_connection(&mut node, link_id, transport_id, 1000); + let identity = seed_completed_connection(&mut node, link_id, transport_id, 1000); let node_addr = *identity.node_addr(); // Simulate a retry entry existing for this peer @@ -1535,7 +1527,6 @@ fn test_promote_clears_retry_pending() { ); assert_eq!(node.peering.reconciler.retry_pending.len(), 1); - node.add_connection(conn).unwrap(); node.promote_connection(link_id, identity, 2000).unwrap(); assert!( diff --git a/src/peer/machine.rs b/src/peer/machine.rs index 1834917..91941ea 100644 --- a/src/peer/machine.rs +++ b/src/peer/machine.rs @@ -2971,3 +2971,22 @@ mod tests { assert_eq!(alloc.count(), 0); } } + +/// T-SANSIO: the action vocabulary must stay plain, comparable data. +/// +/// `PeerAction` is the sans-IO boundary between the pure reducer and its +/// driver. Requiring `Clone + Eq` is a compile-time statement that no variant +/// may carry a runtime handle (a socket, a `JoinHandle`, a channel sender), +/// since none of those are `Clone + Eq`. If a future variant smuggles one in, +/// this bound stops compiling. +#[cfg(test)] +mod action_contract { + use super::PeerAction; + + fn assert_clone_eq() {} + + #[test] + fn peer_action_is_plain_comparable_data() { + assert_clone_eq::(); + } +}