Merge branch 'refactor-node' into refactor-node-next

Re-express the handshake-state carrier collapse onto the XX code: the leg's
handshake_state field is deleted and the displayed state is derived from the
peer machine's phase, with failure carried on the machine (a send_failed flag
that preserves the handshake phase) rather than on the leg. The next projection
maps the SentMsg2 responder phase to received_msg1 and the anonymous-dial
Discovered phase to sent_msg1; the three initiator send-failure sites carry
failure via send_failed. Telemetry strings, wire bytes, index allocation, and
stale-connection reaping are byte-identical to next.
This commit is contained in:
Johnathan Corgan
2026-07-18 04:08:26 +00:00
12 changed files with 322 additions and 254 deletions
+24
View File
@@ -640,7 +640,18 @@ impl Node {
error = %e,
"Handshake completion failed"
);
// Drop the leg's Noise handle (byte-identical point) and
// record the failure on the control machine as `send_failed`
// — the failure state's new home. The machine PHASE stays
// exactly where the old leg-carried failure left it
// (`Handshaking{SentMsg1}`): the stale-connection sweep
// reclaims the leg via the machine `is_failed()` at the next
// tick, before any projection or resend, byte-identical to
// the pre-collapse leg mark.
conn.mark_failed();
if let Some(machine) = self.peer_machines.get_mut(&link_id) {
machine.mark_send_failed();
}
self.stats_mut()
.record_reject(RejectReason::Handshake(HandshakeReject::BadState));
return;
@@ -653,7 +664,13 @@ impl Node {
Ok(()) => {}
Err(e) => {
warn!(link_id = %link_id, our_profile = %our_profile, error = %e, "FMP negotiation failed");
// Failure moves to the machine (`send_failed`); the phase
// stays `Handshaking{SentMsg1}` so the sweep reclaims the
// leg exactly as the pre-collapse leg mark did.
conn.mark_failed();
if let Some(machine) = self.peer_machines.get_mut(&link_id) {
machine.mark_send_failed();
}
self.stats_mut()
.record_reject(RejectReason::Handshake(HandshakeReject::BadState));
return;
@@ -741,9 +758,16 @@ impl Node {
error = %e,
"Failed to send msg3"
);
// Failure moves to the machine (`send_failed`); the phase
// stays `Handshaking{SentMsg1}` (promote has not run yet) so
// the sweep reclaims the leg exactly as the pre-collapse leg
// mark did.
if let Some(conn) = self.leg_mut(&link_id) {
conn.mark_failed();
}
if let Some(machine) = self.peer_machines.get_mut(&link_id) {
machine.mark_send_failed();
}
self.stats_mut()
.record_reject(RejectReason::Handshake(HandshakeReject::BadState));
return;
+12 -6
View File
@@ -19,15 +19,15 @@ impl LifecycleView for Node {
// reap.
self.peer_machines
.iter()
.filter_map(|(link_id, machine)| machine.leg().map(|conn| (link_id, conn)))
.filter(|(link_id, conn)| {
conn.is_failed()
.filter_map(|(link_id, machine)| machine.leg().map(|conn| (link_id, machine, conn)))
.filter(|(link_id, machine, conn)| {
machine.is_failed()
|| (conn.is_timed_out(now_ms, timeout_ms)
&& !self.peer_timers.get(*link_id).is_some_and(|timers| {
timers.contains_key(&TimerKind::HandshakeTimeout)
}))
})
.map(|(link_id, conn)| ConnSnapshot {
.map(|(link_id, _machine, conn)| ConnSnapshot {
link: *link_id,
is_outbound: conn.is_outbound(),
retry_addr: conn.expected_identity().map(|id| *id.node_addr()),
@@ -70,10 +70,16 @@ impl Node {
match action {
ConnAction::ScheduleRetry { peer } => self.note_handshake_timeout(peer, now_ms),
ConnAction::Teardown { link } => {
// Log before cleanup (needs live connection state).
// Log before cleanup (needs live connection state). The
// failure signal is now read from the control machine; the
// leg still carries direction/idle for the log fields.
let is_failed = self
.peer_machines
.get(&link)
.is_some_and(|machine| machine.is_failed());
if let Some(conn) = self.leg(&link) {
let direction = conn.direction();
if conn.is_failed() {
if is_failed {
debug!(
link_id = %link,
direction = %direction,