Relocate PromotionResult into proto/fmp

Move the PromotionResult enum (and its impl) out of peer::mod and into
proto/fmp/core.rs, alongside the cross_connection_winner tie-break helper
that was relocated the same way. This is FMP connection-lifecycle result
vocabulary, so it belongs in the FMP subsystem home rather than the peer
module.

Behavior-neutral pure type relocation: consumers import it from
crate::proto::fmp, and the crate-root public path crate::PromotionResult
is preserved via a re-export in lib.rs (mirroring cross_connection_winner).
Full lib suite green at baseline.
This commit is contained in:
Johnathan Corgan
2026-07-10 16:52:18 +00:00
parent 1c41f73931
commit 1c1ed0d939
7 changed files with 64 additions and 68 deletions
+2 -3
View File
@@ -88,13 +88,12 @@ pub use proto::fmp::HandshakeMessageType;
// Re-export cache types
pub use cache::{CacheEntry, CacheError, CacheStats, CoordCache};
// Re-export FMP tie-break helper (relocated from peer:: to proto::fmp)
pub use proto::fmp::cross_connection_winner;
// Re-export FMP tie-break helper and promotion result (relocated from peer:: to proto::fmp)
pub use proto::fmp::{PromotionResult, cross_connection_winner};
// Re-export peer types
pub use peer::{
ActivePeer, ConnectivityState, HandshakeState, PeerConnection, PeerError, PeerSlot,
PromotionResult,
};
// Re-export node types
+2 -2
View File
@@ -5,11 +5,11 @@ use crate::PeerIdentity;
use crate::node::acl::PeerAclContext;
use crate::node::reject::{HandshakeReject, RejectReason};
use crate::node::{Node, NodeError};
use crate::peer::{ActivePeer, PeerConnection, PromotionResult};
use crate::peer::{ActivePeer, PeerConnection};
use crate::proto::fmp::wire::{Msg1Header, Msg2Header, build_msg2};
use crate::proto::fmp::{
ConnAction, EstablishSnapshot, EstablishView, InboundDecision, InboundReject, OutboundDecision,
OutboundSnapshot, WireOutcome, cross_connection_winner,
OutboundSnapshot, PromotionResult, WireOutcome, cross_connection_winner,
};
use crate::transport::{Link, LinkDirection, LinkId, ReceivedPacket};
use std::time::Duration;
+1 -1
View File
@@ -1002,7 +1002,7 @@ fn build_ipv6_packet(
#[test]
fn test_identity_cache_populated_on_promote() {
use crate::peer::PromotionResult;
use crate::proto::fmp::PromotionResult;
let mut node = make_node();
let transport_id = TransportId::new(1);
+1 -1
View File
@@ -1,6 +1,6 @@
use super::*;
use crate::nostr::{BootstrapEvent, NostrRendezvous};
use crate::peer::PromotionResult;
use crate::proto::fmp::PromotionResult;
use crate::transport::udp::UdpTransport;
use crate::transport::{TransportHandle, packet_channel};
use std::sync::Arc;
+1 -60
View File
@@ -59,66 +59,6 @@ pub enum PeerError {
MaxPeersExceeded { max: usize },
}
// ============================================================================
// Cross-Connection Handling
// ============================================================================
/// Result of attempting to promote a connection to active peer.
///
/// When a handshake completes, we may discover that we already have a
/// connection to this peer (cross-connection). The tie-breaker rule
/// determines which connection survives.
///
/// Note: Returns NodeAddr instead of ActivePeer because ActivePeer cannot
/// be cloned (it contains NoiseSession which has cryptographic state).
/// Callers can look up the peer from the peers map using the NodeAddr.
#[derive(Debug, Clone, Copy)]
pub enum PromotionResult {
/// New peer created successfully.
Promoted(NodeAddr),
/// Cross-connection detected. This connection lost the tie-breaker
/// and should be closed.
CrossConnectionLost {
/// The link that won (existing connection).
winner_link_id: LinkId,
},
/// Cross-connection detected. This connection won the tie-breaker.
/// The existing connection was replaced.
CrossConnectionWon {
/// The link that lost (previous connection, now closed).
loser_link_id: LinkId,
/// The node ID of the peer.
node_addr: NodeAddr,
},
}
impl PromotionResult {
/// Get the node ID if promotion succeeded.
pub fn node_addr(&self) -> Option<NodeAddr> {
match self {
PromotionResult::Promoted(node_addr) => Some(*node_addr),
PromotionResult::CrossConnectionWon { node_addr, .. } => Some(*node_addr),
PromotionResult::CrossConnectionLost { .. } => None,
}
}
/// Check if this connection should be closed.
pub fn should_close_this_connection(&self) -> bool {
matches!(self, PromotionResult::CrossConnectionLost { .. })
}
/// Get the link that should be closed, if any.
pub fn link_to_close(&self) -> Option<LinkId> {
match self {
PromotionResult::CrossConnectionLost { .. } => None, // Caller's link
PromotionResult::CrossConnectionWon { loser_link_id, .. } => Some(*loser_link_id),
PromotionResult::Promoted(_) => None,
}
}
}
// ============================================================================
// PeerSlot
// ============================================================================
@@ -240,6 +180,7 @@ impl fmt::Display for PeerSlot {
#[cfg(test)]
mod tests {
use super::*;
use crate::proto::fmp::PromotionResult;
use crate::transport::LinkId;
use crate::{Identity, PeerIdentity};
+56
View File
@@ -51,6 +51,62 @@ pub fn cross_connection_winner(
}
}
/// Result of attempting to promote a connection to active peer.
///
/// When a handshake completes, we may discover that we already have a
/// connection to this peer (cross-connection). The tie-breaker rule
/// determines which connection survives.
///
/// Note: Returns NodeAddr instead of ActivePeer because ActivePeer cannot
/// be cloned (it contains NoiseSession which has cryptographic state).
/// Callers can look up the peer from the peers map using the NodeAddr.
#[derive(Debug, Clone, Copy)]
pub enum PromotionResult {
/// New peer created successfully.
Promoted(NodeAddr),
/// Cross-connection detected. This connection lost the tie-breaker
/// and should be closed.
CrossConnectionLost {
/// The link that won (existing connection).
winner_link_id: LinkId,
},
/// Cross-connection detected. This connection won the tie-breaker.
/// The existing connection was replaced.
CrossConnectionWon {
/// The link that lost (previous connection, now closed).
loser_link_id: LinkId,
/// The node ID of the peer.
node_addr: NodeAddr,
},
}
impl PromotionResult {
/// Get the node ID if promotion succeeded.
pub fn node_addr(&self) -> Option<NodeAddr> {
match self {
PromotionResult::Promoted(node_addr) => Some(*node_addr),
PromotionResult::CrossConnectionWon { node_addr, .. } => Some(*node_addr),
PromotionResult::CrossConnectionLost { .. } => None,
}
}
/// Check if this connection should be closed.
pub fn should_close_this_connection(&self) -> bool {
matches!(self, PromotionResult::CrossConnectionLost { .. })
}
/// Get the link that should be closed, if any.
pub fn link_to_close(&self) -> Option<LinkId> {
match self {
PromotionResult::CrossConnectionLost { .. } => None, // Caller's link
PromotionResult::CrossConnectionWon { loser_link_id, .. } => Some(*loser_link_id),
PromotionResult::Promoted(_) => None,
}
}
}
/// A snapshot of one handshake connection's lifecycle-relevant state, taken by
/// the shell so the core decides without touching live `Node` state or reading
/// a clock.
+1 -1
View File
@@ -33,12 +33,12 @@ pub(crate) mod wire;
#[cfg(test)]
mod tests;
pub use core::cross_connection_winner;
pub(crate) use core::{
ConnAction, ConnSnapshot, EstablishSnapshot, EstablishView, InboundDecision, InboundReject,
LifecycleView, OutboundDecision, OutboundSnapshot, PeerSnapshot, RekeyCfg, RekeyResendSnapshot,
WireOutcome,
};
pub use core::{PromotionResult, cross_connection_winner};
pub(crate) use limits::backoff_ms;
pub use state::HandshakeState;
pub(crate) use state::{ConnectionState, Fmp};