use anyhow::anyhow;
use dashmap::DashSet;
use either::{Either, Left, Right};
use futures::FutureExt;
use futures::StreamExt;
use std::convert::Infallible;
use std::future::Future;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::pin::Pin;
use std::time::Duration;
use std::{
collections::{BTreeMap, HashMap, HashSet},
sync::Arc,
};
use tokio::net::UdpSocket;
use tokio::sync::mpsc::{self, Receiver, Sender, error::TryRecvError};
use tokio::time::Instant;
use tokio::time::{sleep, timeout};
use tracing::Instrument;
use super::{ConnectionError, EventLoopNotificationsReceiver, NetworkBridge};
use crate::contract::{ContractHandlerEvent, WaitingTransaction};
use crate::message::{NetMessageV1, QueryResult};
use crate::node::network_bridge::handshake::{
Command as HandshakeCommand, CommandSender as HandshakeCommandSender, Event as HandshakeEvent,
HandshakeHandler,
};
use crate::node::network_bridge::priority_select;
use crate::operations::connect::ConnectMsg;
#[cfg(feature = "simulation_tests")]
use crate::ring::PeerKey;
use crate::ring::{Distance, Location};
use crate::transport::{
BroadcastDeliveryOutcome, CongestionControlConfig, ExpectedInboundTracker, PeerConnectionApi,
Socket, TransportError, TransportKeypair, TransportPublicKey, create_connection_handler,
global_bandwidth::GlobalBandwidthManager, peer_connection::StreamId,
};
use crate::{
client_events::ClientId,
config::GlobalExecutor,
contract::{ContractHandlerChannel, ExecutorTransactionStream, SenderHalve, WaitingResolution},
message::{MessageStats, NetMessage, NodeEvent, Transaction, TransactionType},
node::{
NetEventRegister, NodeConfig, OpManager, PeerId, WaiterReply, process_message_decoupled,
},
ring::{KnownPeerKeyLocation, PeerConnectionBackoff, PeerKeyLocation},
tracing::NetEventLog,
};
use freenet_stdlib::client_api::{ContractResponse, HostResponse};
mod broadcast;
mod connection_lifecycle;
mod dispatch;
mod migration;
#[derive(Debug)]
pub enum EventLoopExitReason {
GracefulShutdown,
UnexpectedStreamEnd,
}
impl std::fmt::Display for EventLoopExitReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventLoopExitReason::GracefulShutdown => write!(f, "Graceful shutdown"),
EventLoopExitReason::UnexpectedStreamEnd => {
write!(f, "Network event stream ended unexpectedly")
}
}
}
}
impl std::error::Error for EventLoopExitReason {}
const QUERY_SUBSCRIPTIONS_HANDLER_TIMEOUT: Duration = Duration::from_secs(5);
async fn query_app_subscriptions_bounded(
ch_outbound: &ContractHandlerChannel<SenderHalve>,
) -> Vec<crate::message::SubscriptionInfo> {
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
match timeout(
QUERY_SUBSCRIPTIONS_HANDLER_TIMEOUT,
ch_outbound.send_to_handler(ContractHandlerEvent::QuerySubscriptions { callback: tx }),
)
.await
{
Ok(Ok(_)) => match timeout(Duration::from_secs(1), rx.recv()).await {
Ok(Some(QueryResult::NetworkDebug(info))) => info.application_subscriptions,
_ => Vec::new(),
},
Ok(Err(err)) => {
tracing::warn!(
error = %err,
"QuerySubscriptions diagnostics: contract handler error; \
serving empty app subscriptions"
);
Vec::new()
}
Err(_elapsed) => {
tracing::warn!(
"QuerySubscriptions diagnostics: contract handler did not respond within \
timeout; serving empty app subscriptions"
);
Vec::new()
}
}
}
pub(crate) enum P2pBridgeEvent {
Message(PeerKeyLocation, Box<NetMessage>),
NodeAction(NodeEvent),
StreamSend {
target_addr: SocketAddr,
stream_id: StreamId,
data: bytes::Bytes,
metadata: Option<bytes::Bytes>,
completion_tx: Option<tokio::sync::oneshot::Sender<BroadcastDeliveryOutcome>>,
progress: Option<crate::operations::stream_progress::StreamProgressHandle>,
},
PipeStream {
target_addr: SocketAddr,
outbound_stream_id: StreamId,
inbound_handle: crate::transport::peer_connection::streaming::StreamHandle,
metadata: Option<bytes::Bytes>,
},
}
impl std::fmt::Debug for P2pBridgeEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Message(peer, msg) => f.debug_tuple("Message").field(peer).field(msg).finish(),
Self::NodeAction(action) => f.debug_tuple("NodeAction").field(action).finish(),
Self::StreamSend {
target_addr,
stream_id,
data,
metadata,
..
} => f
.debug_struct("StreamSend")
.field("target_addr", target_addr)
.field("stream_id", stream_id)
.field("data_len", &data.len())
.field("has_metadata", &metadata.is_some())
.finish(),
Self::PipeStream {
target_addr,
outbound_stream_id,
..
} => f
.debug_struct("PipeStream")
.field("target_addr", target_addr)
.field("outbound_stream_id", outbound_stream_id)
.finish(),
}
}
}
#[derive(Clone)]
pub(crate) struct P2pBridge {
#[allow(dead_code)]
accepted_peers: Arc<DashSet<SocketAddr>>,
ev_listener_tx: Sender<P2pBridgeEvent>,
op_manager: Arc<OpManager>,
log_register: Arc<dyn NetEventRegister>,
gateways: Arc<Vec<PeerKeyLocation>>,
}
impl P2pBridge {
fn new<EL>(
sender: Sender<P2pBridgeEvent>,
op_manager: Arc<OpManager>,
event_register: EL,
gateways: Arc<Vec<PeerKeyLocation>>,
) -> Self
where
EL: NetEventRegister,
{
Self {
accepted_peers: Arc::new(DashSet::new()),
ev_listener_tx: sender,
op_manager,
log_register: Arc::new(event_register),
gateways,
}
}
pub(crate) async fn handle_orphaned_transactions(
&self,
transactions: Vec<Transaction>,
disconnected_peer_addr: SocketAddr,
) {
if transactions.is_empty() {
return;
}
tracing::debug!(
count = transactions.len(),
peer = %disconnected_peer_addr,
"Orphaned transactions from pruned connection — waking parked drivers"
);
for tx in transactions {
self.op_manager
.notify_orphaned_transaction(tx, disconnected_peer_addr);
}
}
pub(super) async fn send_stream_with_completion(
&self,
target_addr: SocketAddr,
stream_id: StreamId,
data: bytes::Bytes,
metadata: Option<bytes::Bytes>,
completion_tx: Option<tokio::sync::oneshot::Sender<BroadcastDeliveryOutcome>>,
progress: Option<crate::operations::stream_progress::StreamProgressHandle>,
) -> super::ConnResult<()> {
self.ev_listener_tx
.send(P2pBridgeEvent::StreamSend {
target_addr,
stream_id,
data,
metadata,
completion_tx,
progress,
})
.await
.map_err(|_| ConnectionError::SendNotCompleted(target_addr))?;
Ok(())
}
fn resolve_target(&self, target_addr: SocketAddr) -> PeerKeyLocation {
if let Some(target) = self
.op_manager
.ring
.connection_manager
.get_peer_by_addr(target_addr)
{
return target;
}
if let Some(gw_peer) = self
.gateways
.iter()
.find(|gw| gw.socket_addr() == Some(target_addr))
.cloned()
{
tracing::debug!(
peer_addr = %target_addr,
phase = "send",
"Using gateway public key for reconnection"
);
return gw_peer;
}
tracing::debug!(
peer_addr = %target_addr,
phase = "send",
"Sending to unknown peer address with no known public key"
);
PeerKeyLocation::new(
(*self.op_manager.ring.connection_manager.pub_key).clone(),
target_addr,
)
}
fn try_send(&self, target_addr: SocketAddr, msg: NetMessage) -> super::ConnResult<()> {
let target = self.resolve_target(target_addr);
self.op_manager.sending_transaction(&target, &msg);
match self
.ev_listener_tx
.try_send(P2pBridgeEvent::Message(target, Box::new(msg)))
{
Ok(()) => Ok(()),
Err(mpsc::error::TrySendError::Full(_)) => {
tracing::warn!(
peer_addr = %target_addr,
phase = "backpressure",
"Event-loop bridge channel full; dropped outbound message from an \
on-loop caller (will be recovered by op retry / periodic re-sync). \
Sustained occurrences indicate fan-out / migration load."
);
Err(ConnectionError::SendNotCompleted(target_addr))
}
Err(mpsc::error::TrySendError::Closed(_)) => {
tracing::debug!(
peer_addr = %target_addr,
phase = "send",
"Event-loop bridge channel closed; dropping outbound message"
);
Err(ConnectionError::SendNotCompleted(target_addr))
}
}
}
}
impl NetworkBridge for P2pBridge {
async fn drop_connection(&mut self, peer_addr: SocketAddr) -> super::ConnResult<()> {
if self.accepted_peers.remove(&peer_addr).is_some() {
self.ev_listener_tx
.send(P2pBridgeEvent::NodeAction(NodeEvent::DropConnection(
peer_addr,
)))
.await
.map_err(|_| ConnectionError::SendNotCompleted(peer_addr))?;
if let Some(peer_loc) = self
.op_manager
.ring
.connection_manager
.get_peer_by_addr(peer_addr)
{
if let Ok(known_loc) = KnownPeerKeyLocation::try_from(&peer_loc) {
use crate::tracing::DisconnectReason;
let connection_duration_ms = self
.op_manager
.ring
.connection_manager
.get_connection_duration_ms(peer_addr);
if let Some(event) = NetEventLog::disconnected_with_context(
&self.op_manager.ring,
&PeerId::new(peer_loc.pub_key().clone(), known_loc.socket_addr()),
DisconnectReason::RemoteDropped,
connection_duration_ms,
None, None, ) {
self.log_register.register_events(Either::Left(event)).await;
}
}
}
}
Ok(())
}
async fn send(&self, target_addr: SocketAddr, msg: NetMessage) -> super::ConnResult<()> {
let target = self.resolve_target(target_addr);
self.op_manager.sending_transaction(&target, &msg);
self.ev_listener_tx
.send(P2pBridgeEvent::Message(target, Box::new(msg)))
.await
.map_err(|_| ConnectionError::SendNotCompleted(target_addr))?;
Ok(())
}
async fn send_stream(
&self,
target_addr: SocketAddr,
stream_id: StreamId,
data: bytes::Bytes,
metadata: Option<bytes::Bytes>,
) -> super::ConnResult<()> {
self.ev_listener_tx
.send(P2pBridgeEvent::StreamSend {
target_addr,
stream_id,
data,
metadata,
completion_tx: None,
progress: None,
})
.await
.map_err(|_| ConnectionError::SendNotCompleted(target_addr))?;
Ok(())
}
async fn send_stream_with_progress(
&self,
target_addr: SocketAddr,
stream_id: StreamId,
data: bytes::Bytes,
metadata: Option<bytes::Bytes>,
progress: Option<crate::operations::stream_progress::StreamProgressHandle>,
) -> super::ConnResult<()> {
self.ev_listener_tx
.send(P2pBridgeEvent::StreamSend {
target_addr,
stream_id,
data,
metadata,
completion_tx: None,
progress,
})
.await
.map_err(|_| ConnectionError::SendNotCompleted(target_addr))?;
Ok(())
}
async fn pipe_stream(
&self,
target_addr: SocketAddr,
outbound_stream_id: StreamId,
inbound_handle: crate::transport::peer_connection::streaming::StreamHandle,
metadata: Option<bytes::Bytes>,
) -> super::ConnResult<()> {
self.ev_listener_tx
.send(P2pBridgeEvent::PipeStream {
target_addr,
outbound_stream_id,
inbound_handle,
metadata,
})
.await
.map_err(|_| ConnectionError::SendNotCompleted(target_addr))?;
Ok(())
}
}
type PeerConnChannelSender = Sender<Either<NetMessage, ConnEvent>>;
type PeerConnChannelRecv = Receiver<Either<NetMessage, ConnEvent>>;
#[derive(Debug)]
struct ConnectionEntry {
sender: PeerConnChannelSender,
pub_key: Option<TransportPublicKey>,
connection_id: u64,
created_at: Instant,
remote_version: Option<(u8, u8, u16)>,
}
fn is_zombie(
created_at_elapsed: Duration,
in_ring: bool,
has_pending: bool,
is_gateway: bool,
transient_ttl: Duration,
) -> bool {
let zombie_threshold = transient_ttl * 3;
let absolute_zombie_threshold = transient_ttl * 6;
if in_ring {
return false;
}
const GATEWAY_ZOMBIE_EXEMPTION: Duration = Duration::from_secs(3600);
if is_gateway && created_at_elapsed < GATEWAY_ZOMBIE_EXEMPTION {
return false;
}
if created_at_elapsed > zombie_threshold && !has_pending {
return true;
}
if created_at_elapsed > absolute_zombie_threshold {
return true;
}
false
}
static NEXT_CONNECTION_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
fn next_connection_id() -> u64 {
NEXT_CONNECTION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
trait ContractPresenceOracle {
fn is_hosting_contract(&self, key: &freenet_stdlib::prelude::ContractKey) -> bool;
fn is_subscribed(&self, key: &freenet_stdlib::prelude::ContractKey) -> bool;
fn hosting_contract_keys(&self) -> Vec<freenet_stdlib::prelude::ContractKey>;
fn hosting_contract_size(&self, key: &freenet_stdlib::prelude::ContractKey) -> u64;
}
impl ContractPresenceOracle for crate::ring::Ring {
fn is_hosting_contract(&self, key: &freenet_stdlib::prelude::ContractKey) -> bool {
crate::ring::Ring::is_hosting_contract(self, key)
}
fn is_subscribed(&self, key: &freenet_stdlib::prelude::ContractKey) -> bool {
crate::ring::Ring::is_subscribed(self, key)
}
fn hosting_contract_keys(&self) -> Vec<freenet_stdlib::prelude::ContractKey> {
crate::ring::Ring::hosting_contract_keys(self)
}
fn hosting_contract_size(&self, key: &freenet_stdlib::prelude::ContractKey) -> u64 {
crate::ring::Ring::hosting_contract_size(self, key)
}
}
fn collect_contract_states<O: ContractPresenceOracle + ?Sized>(
oracle: &O,
contract_keys: &[freenet_stdlib::prelude::ContractKey],
) -> std::collections::HashMap<String, freenet_stdlib::client_api::ContractState> {
use freenet_stdlib::client_api::ContractState;
let mut states = std::collections::HashMap::new();
if contract_keys.is_empty() {
for contract_key in oracle.hosting_contract_keys() {
let is_subscribed = oracle.is_subscribed(&contract_key);
let subscriber_count = if is_subscribed { 1 } else { 0 };
states.insert(
contract_key.to_string(),
ContractState {
subscribers: subscriber_count as u32,
subscriber_peer_ids: Vec::new(),
size_bytes: oracle.hosting_contract_size(&contract_key),
},
);
}
} else {
for contract_key in contract_keys {
let is_hosting = oracle.is_hosting_contract(contract_key);
let is_subscribed = oracle.is_subscribed(contract_key);
if !is_hosting && !is_subscribed {
continue;
}
let subscriber_count = if is_subscribed { 1 } else { 0 };
states.insert(
contract_key.to_string(),
ContractState {
subscribers: subscriber_count as u32,
subscriber_peer_ids: Vec::new(),
size_bytes: oracle.hosting_contract_size(contract_key),
},
);
}
}
states
}
pub(in crate::node) struct P2pConnManager {
pub(in crate::node) gateways: Vec<PeerKeyLocation>,
pub(in crate::node) bridge: P2pBridge,
conn_bridge_rx: Receiver<P2pBridgeEvent>,
event_listener: Box<dyn NetEventRegister>,
connections: BTreeMap<SocketAddr, ConnectionEntry>,
addr_by_pub_key: BTreeMap<TransportPublicKey, SocketAddr>,
conn_event_tx: Option<Sender<ConnEvent>>,
key_pair: TransportKeypair,
listening_ip: IpAddr,
listening_port: u16,
is_gateway: bool,
this_location: Option<Location>,
check_version: bool,
bandwidth_limit: Option<usize>,
global_bandwidth: Option<Arc<GlobalBandwidthManager>>,
ledbat_min_ssthresh: Option<usize>,
congestion_config: CongestionControlConfig,
blocked_addresses: Option<HashSet<SocketAddr>>,
broadcast_retries: HashMap<freenet_stdlib::prelude::ContractKey, u8>,
broadcast_no_target_streak: HashMap<freenet_stdlib::prelude::ContractKey, u32>,
#[cfg(not(feature = "simulation_tests"))]
broadcast_queue: super::broadcast_queue::BroadcastQueue,
}
pub(crate) const SUBSCRIBE_HINT_MIN_VERSION: (u8, u8, u16) = (0, 2, 80);
pub(crate) const PLACEMENT_MIGRATION_ENABLED: bool = false;
pub(crate) fn placement_migration_enabled(floor_override: Option<(u8, u8, u16)>) -> bool {
floor_override.is_some() || PLACEMENT_MIGRATION_ENABLED
}
pub(crate) const fn own_crate_version() -> (u8, u8, u16) {
parse_crate_version(env!("CARGO_PKG_VERSION"))
}
const fn parse_crate_version(version: &str) -> (u8, u8, u16) {
let bytes = version.as_bytes();
let mut major = 0u8;
let mut minor = 0u8;
let mut patch = 0u16;
let mut state = 0u8; let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if c == b'.' {
state += 1;
} else if c.is_ascii_digit() {
let digit = (c - b'0') as u16;
match state {
0 => major = (major as u16 * 10 + digit) as u8,
1 => minor = (minor as u16 * 10 + digit) as u8,
2 => patch = patch * 10 + digit,
_ => {}
}
} else {
break; }
i += 1;
}
(major, minor, patch)
}
pub(crate) fn version_supports_subscribe_hint(
remote: Option<(u8, u8, u16)>,
floor: (u8, u8, u16),
) -> bool {
remote.is_some_and(|v| v >= floor)
}
const MIGRATION_SCAN_CAP_PER_NEW_PEER: usize = 32;
fn pick_closest_migration_target<'a, I>(
contract_loc: Location,
my_dist: Distance,
hosting: &HashSet<TransportPublicKey>,
candidates: I,
) -> Option<PeerKeyLocation>
where
I: IntoIterator<Item = &'a PeerKeyLocation>,
{
let mut best: Option<(Distance, &'a TransportPublicKey, &'a PeerKeyLocation)> = None;
for pkl in candidates {
if pkl.socket_addr().is_none() {
continue;
}
if hosting.contains(pkl.pub_key()) {
continue;
}
let Some(pkl_loc) = pkl.location() else {
continue;
};
let dist = contract_loc.distance(pkl_loc);
if dist >= my_dist {
continue;
}
let candidate_pk = pkl.pub_key();
let replace = match &best {
None => true,
Some((best_dist, best_pk, _)) => {
dist < *best_dist || (dist == *best_dist && candidate_pk < *best_pk)
}
};
if replace {
best = Some((dist, candidate_pk, pkl));
}
}
best.map(|(_, _, pkl)| pkl.clone())
}
impl P2pConnManager {
pub(in crate::node) fn listening_port(&self) -> u16 {
self.listening_port
}
pub async fn build(
config: &NodeConfig,
op_manager: Arc<OpManager>,
event_listener: impl NetEventRegister + Clone,
) -> anyhow::Result<Self> {
let listen_port = config.network_listener_port;
let listener_ip = config.network_listener_ip;
let gateways = config.get_gateways()?;
let gateways_arc = Arc::new(gateways.clone());
let key_pair = config.key_pair.clone();
let (tx_bridge_cmd, rx_bridge_cmd) = mpsc::channel(512);
let bridge = P2pBridge::new(
tx_bridge_cmd,
op_manager,
event_listener.clone(),
gateways_arc,
);
let advertised_addr = {
let advertised_ip = config
.own_addr
.map(|addr| addr.ip())
.or(config.config.network_api.public_address)
.unwrap_or_else(|| {
if listener_ip.is_unspecified() {
IpAddr::V4(Ipv4Addr::LOCALHOST)
} else {
listener_ip
}
});
let advertised_port = config
.own_addr
.map(|addr| addr.port())
.or(config.config.network_api.public_port)
.unwrap_or(listen_port);
SocketAddr::new(advertised_ip, advertised_port)
};
bridge
.op_manager
.ring
.connection_manager
.try_set_own_addr(advertised_addr);
Ok(P2pConnManager {
gateways,
bridge,
conn_bridge_rx: rx_bridge_cmd,
event_listener: Box::new(event_listener),
connections: BTreeMap::new(),
addr_by_pub_key: BTreeMap::new(),
conn_event_tx: None,
key_pair,
listening_ip: listener_ip,
listening_port: listen_port,
is_gateway: config.is_gateway,
this_location: config.location,
check_version: !config.config.network_api.ignore_protocol_version,
bandwidth_limit: config.config.network_api.bandwidth_limit,
global_bandwidth: config
.config
.network_api
.total_bandwidth_limit
.map(|total| {
let manager = Arc::new(GlobalBandwidthManager::new(
total,
config.config.network_api.min_bandwidth_per_connection,
));
crate::transport::shadow_demand::register_global_bandwidth(&manager);
manager
}),
ledbat_min_ssthresh: config.config.network_api.ledbat_min_ssthresh,
congestion_config: config.config.network_api.build_congestion_config(),
blocked_addresses: config.blocked_addresses.clone(),
broadcast_retries: HashMap::new(),
broadcast_no_target_streak: HashMap::new(),
#[cfg(not(feature = "simulation_tests"))]
broadcast_queue: super::broadcast_queue::BroadcastQueue::new(),
})
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(name = "network_event_listener", fields(peer = %self.bridge.op_manager.ring.connection_manager.pub_key), skip_all)]
pub async fn run_event_listener(
self,
op_manager: Arc<OpManager>,
client_wait_for_transaction: ContractHandlerChannel<WaitingResolution>,
notification_channel: EventLoopNotificationsReceiver,
node_controller: Receiver<NodeEvent>,
) -> anyhow::Result<Infallible> {
self.run_event_listener_with_socket::<UdpSocket>(
op_manager,
client_wait_for_transaction,
notification_channel,
node_controller,
)
.await
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(name = "network_event_listener", fields(peer = %self.bridge.op_manager.ring.connection_manager.pub_key), skip_all)]
pub async fn run_event_listener_with_socket<S: Socket>(
self,
op_manager: Arc<OpManager>,
client_wait_for_transaction: ContractHandlerChannel<WaitingResolution>,
notification_channel: EventLoopNotificationsReceiver,
node_controller: Receiver<NodeEvent>,
) -> anyhow::Result<Infallible> {
let P2pConnManager {
gateways,
bridge,
conn_bridge_rx,
event_listener,
connections,
addr_by_pub_key,
conn_event_tx: _,
key_pair,
listening_ip,
listening_port,
is_gateway,
this_location,
check_version,
bandwidth_limit,
global_bandwidth,
ledbat_min_ssthresh,
congestion_config,
blocked_addresses,
broadcast_retries,
broadcast_no_target_streak,
#[cfg(not(feature = "simulation_tests"))]
broadcast_queue,
} = self;
let (outbound_conn_handler, inbound_conn_handler, mut listen_task_handle) =
create_connection_handler::<S>(
key_pair.clone(),
listening_ip,
listening_port,
is_gateway,
bandwidth_limit,
global_bandwidth,
ledbat_min_ssthresh,
Some(congestion_config.clone()),
)
.await?;
tracing::info!(
listening_port,
listening_ip = %listening_ip,
is_gateway,
peer_key = %key_pair.public(),
phase = "startup",
"Network listener opened - ready to receive connections"
);
let expected_inbound = outbound_conn_handler.expected_inbound_tracker();
let mut state = EventListenerState::new(expected_inbound);
let (conn_event_tx, conn_event_rx) = mpsc::channel(1024);
let peer_ready = if !is_gateway {
Some(bridge.op_manager.peer_ready.clone())
} else {
None
};
let (handshake_handler, handshake_cmd_sender) = HandshakeHandler::new(
inbound_conn_handler,
outbound_conn_handler.clone(),
bridge.op_manager.ring.connection_manager.clone(),
bridge.op_manager.ring.router.clone(),
this_location,
is_gateway,
peer_ready,
);
let select_stream = priority_select::ProductionPrioritySelectStream::new(
notification_channel.notifications_receiver,
notification_channel.op_execution_receiver,
conn_bridge_rx,
handshake_handler,
node_controller,
client_wait_for_transaction,
ExecutorTransactionStream,
conn_event_rx,
);
tokio::pin!(select_stream);
let mut ctx = P2pConnManager {
gateways,
bridge,
conn_bridge_rx: tokio::sync::mpsc::channel(1).1, event_listener,
connections,
addr_by_pub_key,
conn_event_tx: Some(conn_event_tx.clone()),
key_pair,
listening_ip,
listening_port,
is_gateway,
this_location,
check_version,
bandwidth_limit,
global_bandwidth: None, ledbat_min_ssthresh,
congestion_config, blocked_addresses,
broadcast_retries,
broadcast_no_target_streak,
#[cfg(not(feature = "simulation_tests"))]
broadcast_queue: broadcast_queue.clone(),
};
#[cfg(not(feature = "simulation_tests"))]
let _broadcast_worker_handle =
broadcast_queue.start_worker(ctx.bridge.clone(), op_manager.clone());
let mut graceful_shutdown = false;
let mut loop_iteration_count = 0u64;
let mut slow_event_count = 0u64;
let mut last_stats_log = Instant::now();
const STATS_LOG_INTERVAL: Duration = Duration::from_secs(30);
const SLOW_EVENT_THRESHOLD: Duration = Duration::from_millis(100);
loop {
let result = tokio::select! {
biased;
listen_result = &mut listen_task_handle => {
match listen_result {
Ok(e) => {
tracing::error!(
error = %e,
"CRITICAL: UDP listen task exited with transport error"
);
}
Err(join_err) => {
let cause = if join_err.is_panic() { "panicked" } else { "cancelled" };
tracing::error!(
cause,
"CRITICAL: UDP listen task failed: {join_err}"
);
}
}
return Err(anyhow!(
"UDP listen task exited unexpectedly — \
transport layer is dead, node must restart"
));
},
maybe_result = StreamExt::next(&mut select_stream) => {
let Some(result) = maybe_result else {
break;
};
result
},
};
loop_iteration_count += 1;
let event_type = match &result {
priority_select::SelectResult::Notification(_) => "notification",
priority_select::SelectResult::OpExecution(_) => "op_execution",
priority_select::SelectResult::PeerConnection(_) => "peer_connection",
priority_select::SelectResult::ConnBridge(_) => "conn_bridge",
priority_select::SelectResult::Handshake(_) => "handshake",
priority_select::SelectResult::NodeController(_) => "node_controller",
priority_select::SelectResult::ClientTransaction(_) => "client_transaction",
priority_select::SelectResult::ExecutorTransaction(_) => "executor_transaction",
};
let process_start = Instant::now();
let event = ctx
.process_select_result(result, &mut state, &handshake_cmd_sender)
.await?;
let elapsed = process_start.elapsed();
if elapsed > SLOW_EVENT_THRESHOLD {
slow_event_count += 1;
tracing::warn!(
event_type,
elapsed_ms = elapsed.as_millis(),
"Slow event loop iteration"
);
}
if last_stats_log.elapsed() > STATS_LOG_INTERVAL {
let notifier = &op_manager.to_event_listener;
let transport_connections = ctx.connections.len();
let ring_connections = op_manager.ring.connection_manager.connection_count();
tracing::info!(
iterations = loop_iteration_count,
slow_events = slow_event_count,
notification_channel_pending = notifier.notification_channel_pending(),
notification_channel_capacity = notifier.notifications_sender.capacity(),
active_connections = transport_connections,
ring_connections,
"Event loop stats"
);
if transport_connections > 0 && ring_connections == 0 {
tracing::error!(
transport_connections,
ring_connections,
"RING_TRANSPORT_DESYNC: transport has connections but ring topology is empty - \
connections are not being promoted or are being immediately pruned"
);
}
if op_manager.ring.connection_manager.min_ready_connections > 0
&& op_manager.ring.connection_manager.is_self_ready()
{
ctx.handle_broadcast_ready_state(true).await;
}
#[cfg(all(unix, feature = "jemalloc-prof"))]
{
use tikv_jemalloc_ctl::{epoch, stats};
epoch::advance().ok();
let allocated = stats::allocated::read().unwrap_or(0);
let resident = stats::resident::read().unwrap_or(0);
tracing::info!(
allocated_mb = allocated / (1024 * 1024),
resident_mb = resident / (1024 * 1024),
"jemalloc memory stats"
);
}
loop_iteration_count = 0;
slow_event_count = 0;
last_stats_log = Instant::now();
const MAX_ZOMBIE_CLEANUP_PER_CYCLE: usize = 64;
let transient_ttl = op_manager.ring.connection_manager.transient_ttl();
let zombie_addrs: Vec<SocketAddr> = ctx
.connections
.iter()
.filter(|(addr, entry)| {
let is_gateway = ctx
.gateways
.iter()
.any(|gw| gw.socket_addr() == Some(**addr));
is_zombie(
entry.created_at.elapsed(),
op_manager.ring.connection_manager.is_in_ring(**addr),
op_manager
.ring
.connection_manager
.has_connection_or_pending(**addr),
is_gateway,
transient_ttl,
)
})
.map(|(addr, _)| *addr)
.take(MAX_ZOMBIE_CLEANUP_PER_CYCLE)
.collect();
if !zombie_addrs.is_empty() {
tracing::info!(
zombie_count = zombie_addrs.len(),
"Cleaning up zombie transports (not promoted to ring)"
);
}
for addr in &zombie_addrs {
ctx.drop_zombie_connection(*addr, &handshake_cmd_sender)
.await;
}
const PENDING_OP_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
if state.last_pending_op_cleanup.elapsed() > PENDING_OP_CLEANUP_INTERVAL {
let before = state.pending_op_results.len();
state
.pending_op_results
.retain(|_tx, sender| !sender.is_closed());
let removed = before - state.pending_op_results.len();
if removed > 0 {
for _ in 0..removed {
crate::config::GlobalTestMetrics::record_pending_op_remove();
}
crate::config::GlobalTestMetrics::record_pending_op_size(
state.pending_op_results.len() as u64,
);
tracing::info!(
removed,
remaining = state.pending_op_results.len(),
"Cleaned up closed pending_op_results senders"
);
}
state.last_pending_op_cleanup = Instant::now();
}
}
match event {
EventResult::Continue => continue,
EventResult::Event(event) => {
match *event {
ConnEvent::InboundMessage(inbound) => {
let remote = inbound.remote_addr;
let msg = inbound.msg;
tracing::debug!(
tx = %msg.id(),
msg_type = %msg,
peer_addr = ?remote,
direction = "inbound",
phase = "receive",
"Received inbound message from peer"
);
ctx.handle_inbound_message(msg, remote, &op_manager, &mut state)
.await?;
}
ConnEvent::OutboundMessage(NetMessage::V1(NetMessageV1::Aborted(tx))) => {
tracing::warn!(tx = %tx, phase = "abort", "Transaction aborted");
}
ConnEvent::OutboundMessage(msg) => {
tracing::error!(
tx = %msg.id(),
msg_type = %msg,
phase = "send",
"Outbound message missing target - bug in routing logic, processing locally as fallback"
);
ctx.handle_inbound_message(msg, None, &op_manager, &mut state)
.await?;
}
ConnEvent::OutboundMessageWithTarget { target_addr, msg } => {
tracing::debug!(
tx = %msg.id(),
msg_type = %msg,
peer_addr = %target_addr,
direction = "outbound",
phase = "send",
"Sending outbound message to peer"
);
ctx.bridge
.log_register
.register_events(NetEventLog::from_outbound_msg(
&msg,
&ctx.bridge.op_manager.ring,
Some(target_addr),
))
.await;
let peer_connection = ctx.connections.get(&target_addr);
match peer_connection {
Some(peer_connection) => {
const SEND_TIMEOUT: std::time::Duration =
std::time::Duration::from_millis(500);
let msg_type = match &msg {
NetMessage::V1(v1) => match v1 {
NetMessageV1::Connect(_) => "Connect",
NetMessageV1::Put(_) => "Put",
NetMessageV1::Get(_) => "Get",
NetMessageV1::Subscribe(_) => "Subscribe",
NetMessageV1::Update(_) => "Update",
NetMessageV1::Aborted(_) => "Aborted",
NetMessageV1::NeighborHosting { .. } => {
"NeighborHosting"
}
NetMessageV1::InterestSync { .. } => "InterestSync",
NetMessageV1::ReadyState { .. } => "ReadyState",
NetMessageV1::SubscribeHint(_) => "SubscribeHint",
},
};
match tokio::time::timeout(
SEND_TIMEOUT,
peer_connection.sender.send(Left(msg.clone())),
)
.await
{
Ok(Ok(())) => {
tracing::trace!(
tx = %msg.id(),
peer_addr = %target_addr,
phase = "send",
"Message sent to peer connection"
);
}
Ok(Err(_closed)) => {
tracing::error!(
tx = %msg.id(),
peer_addr = %target_addr,
msg_type,
phase = "error",
"Peer connection channel closed"
);
}
Err(_timeout) => {
tracing::warn!(
tx = %msg.id(),
peer_addr = %target_addr,
msg_type,
phase = "backpressure",
"Outbound channel full after {SEND_TIMEOUT:?}, \
dropping message to avoid event loop stall"
);
}
}
}
None => {
let tx = *msg.id();
let target_peer = ctx.bridge.op_manager.peek_target_peer(&tx);
let target_peer = target_peer.or_else(|| {
ctx.bridge
.op_manager
.ring
.connection_manager
.get_peer_location_by_addr(target_addr)
});
let target_peer = target_peer.or_else(|| {
ctx.gateways
.iter()
.find(|gw| gw.socket_addr() == Some(target_addr))
.cloned()
});
let Some(target_peer) = target_peer else {
tracing::warn!(
tx = %tx,
peer_addr = %target_addr,
active_connections = ctx.connections.len(),
phase = "connect",
"Cannot establish connection - peer not found in connection_manager. \
Connection likely dropped before response could be routed."
);
ctx.bridge.op_manager.completed(tx);
continue;
};
tracing::debug!(
tx = %tx,
peer_addr = %target_addr,
peer = %target_peer,
active_connections = ctx.connections.len(),
phase = "connect",
"No existing connection - establishing connection before sending message"
);
let (callback, mut result) = mpsc::channel(10);
let msg_clone = msg.clone();
let bridge_sender = ctx.bridge.ev_listener_tx.clone();
let gateways = ctx.gateways.clone();
let is_gw = gateways
.iter()
.any(|gw| gw.socket_addr() == Some(target_addr));
if let Err(err) = ctx.bridge.ev_listener_tx.try_send(
P2pBridgeEvent::NodeAction(NodeEvent::ConnectPeer {
peer: target_peer.clone(),
tx,
callback,
is_gw,
}),
) {
let reason = match err {
mpsc::error::TrySendError::Full(_) => "full",
mpsc::error::TrySendError::Closed(_) => "closed",
};
tracing::warn!(
tx = %tx,
peer_addr = %target_addr,
reason,
phase = "backpressure",
"Event-loop bridge channel {reason} while dispatching \
ConnectPeer; dropping outbound message (op will retry). \
Sustained occurrences indicate fan-out / migration load \
overwhelming the event loop."
);
continue;
}
tracing::debug!(
tx = %tx,
peer_addr = %target_addr,
phase = "connect",
"Dispatched ConnectPeer event - waiting for connection establishment"
);
let target_peer_for_resend = target_peer.clone();
GlobalExecutor::spawn(async move {
match timeout(Duration::from_secs(20), result.recv()).await
{
Ok(Some(Ok((connected_addr, _)))) => {
tracing::debug!(
tx = %tx,
peer_addr = %connected_addr,
phase = "connect",
"Connection established - rescheduling message send"
);
let connected_peer = PeerKeyLocation::new(
target_peer_for_resend.pub_key().clone(),
connected_addr,
);
if let Err(e) = bridge_sender
.send(P2pBridgeEvent::Message(
connected_peer,
Box::new(msg_clone),
))
.await
{
tracing::error!(
tx = %tx,
peer_addr = %connected_addr,
error = ?e,
phase = "error",
"Failed to reschedule message after connection established"
);
}
}
Ok(Some(Err(e))) => {
tracing::debug!(
tx = %tx,
peer_addr = %target_addr,
error = ?e,
phase = "error",
"Connection attempt failed"
);
}
Ok(None) => {
tracing::error!(
tx = %tx,
peer_addr = %target_addr,
phase = "error",
"Response channel closed before connection result received"
);
}
Err(_) => {
tracing::error!(
tx = %tx,
peer_addr = %target_addr,
phase = "timeout",
"Timeout waiting for connection establishment"
);
}
}
});
}
}
}
ConnEvent::TransportClosed {
remote_addr,
error,
connection_id,
} => {
tracing::debug!(
peer_addr = %remote_addr,
error = ?error,
connection_id,
phase = "disconnect",
"Transport connection closed"
);
}
ConnEvent::StreamSend {
target_addr,
stream_id,
data,
metadata,
completion_tx,
progress,
} => {
const SEND_TIMEOUT: std::time::Duration =
std::time::Duration::from_millis(500);
if let Some(peer_connection) = ctx.connections.get(&target_addr) {
match tokio::time::timeout(
SEND_TIMEOUT,
peer_connection.sender.reserve(),
)
.await
{
Ok(Ok(permit)) => {
permit.send(Right(ConnEvent::StreamSend {
target_addr,
stream_id,
data,
metadata,
completion_tx,
progress,
}));
}
Ok(Err(_closed)) => {
tracing::error!(
stream_id = %stream_id,
peer_addr = %target_addr,
phase = "error",
"Peer connection channel closed; \
cannot route stream fragment"
);
if let Some(tx) = completion_tx {
let _ignored =
tx.send(BroadcastDeliveryOutcome::Dropped);
}
}
Err(_timeout) => {
tracing::warn!(
stream_id = %stream_id,
peer_addr = %target_addr,
timeout_ms = SEND_TIMEOUT.as_millis() as u64,
phase = "backpressure",
"Peer congested; dropping stream fragment \
to keep event loop responsive"
);
if let Some(tx) = completion_tx {
let _ignored =
tx.send(BroadcastDeliveryOutcome::Dropped);
}
}
}
} else {
tracing::warn!(
stream_id = %stream_id,
peer_addr = %target_addr,
phase = "error",
"No connection found for stream send target"
);
if let Some(tx) = completion_tx {
let _ignored = tx.send(BroadcastDeliveryOutcome::Dropped);
}
}
}
ConnEvent::PipeStream {
target_addr,
outbound_stream_id,
inbound_handle,
metadata,
} => {
const SEND_TIMEOUT: std::time::Duration =
std::time::Duration::from_millis(500);
if let Some(peer_connection) = ctx.connections.get(&target_addr) {
match tokio::time::timeout(
SEND_TIMEOUT,
peer_connection.sender.reserve(),
)
.await
{
Ok(Ok(permit)) => {
permit.send(Right(ConnEvent::PipeStream {
target_addr,
outbound_stream_id,
inbound_handle,
metadata,
}));
}
Ok(Err(_closed)) => {
tracing::error!(
stream_id = %outbound_stream_id,
peer_addr = %target_addr,
phase = "error",
"Peer connection channel closed; cannot pipe stream"
);
}
Err(_timeout) => {
tracing::warn!(
stream_id = %outbound_stream_id,
peer_addr = %target_addr,
timeout_ms = SEND_TIMEOUT.as_millis() as u64,
phase = "backpressure",
"Peer congested; dropping stream pipe \
to keep event loop responsive"
);
}
}
} else {
tracing::warn!(
stream_id = %outbound_stream_id,
peer_addr = %target_addr,
phase = "error",
"No connection found for pipe stream target"
);
}
}
ConnEvent::ClosedChannel(reason) => {
match reason {
ChannelCloseReason::Bridge
| ChannelCloseReason::Controller
| ChannelCloseReason::Notification
| ChannelCloseReason::OpExecution => {
let is_gateway = ctx.bridge.op_manager.ring.is_gateway();
let active_conns = ctx.connections.len();
eprintln!(
"CRITICAL: Channel closed reason={reason:?} is_gateway={is_gateway} active_connections={active_conns} - shutting down"
);
tracing::error!(
reason = ?reason,
is_gateway,
active_connections = active_conns,
phase = "shutdown",
"Critical channel closed - performing cleanup and shutting down"
);
let peers_to_cleanup: Vec<_> = ctx
.connections
.iter()
.map(|(addr, entry)| (*addr, entry.pub_key.clone()))
.collect();
for (peer_addr, pub_key_opt) in peers_to_cleanup {
tracing::debug!(
peer_addr = %peer_addr,
phase = "cleanup",
"Cleaning up connection due to channel closure"
);
let peer = if let Some(pub_key) = pub_key_opt.clone() {
PeerKeyLocation::new(pub_key, peer_addr)
} else {
PeerKeyLocation::new(
(*ctx
.bridge
.op_manager
.ring
.connection_manager
.pub_key)
.clone(),
peer_addr,
)
};
let prune_result = ctx
.bridge
.op_manager
.ring
.prune_connection(PeerId::new(
peer.pub_key().clone(),
peer_addr,
))
.await;
ctx.bridge
.handle_orphaned_transactions(
prune_result.orphaned_transactions,
peer_addr,
)
.await;
if prune_result.became_unready {
ctx.handle_broadcast_ready_state(false).await;
}
if let Some(ref pub_key) = pub_key_opt {
ctx.bridge.op_manager.on_ring_connection_lost(pub_key);
}
tracing::trace!(
peer_addr = %peer_addr,
conn_map_size = ctx.connections.len(),
reason = "channel_closed",
"Removing connection from tracking map"
);
ctx.connections.remove(&peer_addr);
if let Some(pub_key) = pub_key_opt {
ctx.addr_by_pub_key.remove(&pub_key);
}
if !handshake_cmd_sender.try_send(
HandshakeCommand::DropConnection { peer: peer.clone() },
) {
tracing::warn!(
peer = %peer,
phase = "cleanup",
"Handshake command channel full/closed during \
cleanup; skipping redundant drop notification"
);
}
}
tracing::debug!(
awaiting_count = state.awaiting_connection.len(),
phase = "cleanup",
"Cleaning up in-progress connection reservations"
);
for (addr, callbacks) in state.awaiting_connection.drain() {
tracing::debug!(
peer_addr = %addr,
callbacks = callbacks.len(),
phase = "cleanup",
"Notifying awaiting connection of shutdown"
);
for mut callback in callbacks {
#[allow(clippy::let_underscore_must_use)]
let _ = callback.send_result(Err(())).await;
}
}
let pending_count = state.pending_op_results.len();
if pending_count > 0 {
tracing::debug!(
pending_count,
phase = "cleanup",
"Draining pending_op_results"
);
for _ in 0..pending_count {
crate::config::GlobalTestMetrics::record_pending_op_remove();
}
state.pending_op_results.clear();
}
tracing::info!(
phase = "shutdown",
"Cleanup complete - exiting event loop"
);
graceful_shutdown = true;
break;
}
}
}
ConnEvent::NodeAction(action) => match action {
NodeEvent::DropConnection(peer_addr) => {
if !ctx
.drop_connection_by_addr(peer_addr, &handshake_cmd_sender)
.await
{
tracing::debug!(
peer_addr = %peer_addr,
phase = "disconnect",
"Drop connection requested for unknown address - ignoring"
);
}
}
NodeEvent::DropAllConnections => {
let all_addrs: Vec<SocketAddr> =
ctx.connections.keys().copied().collect();
tracing::warn!(
count = all_addrs.len(),
"DropAllConnections: closing all connections (suspend/resume recovery)"
);
for peer_addr in all_addrs {
ctx.drop_connection_by_addr(peer_addr, &handshake_cmd_sender)
.await;
}
}
NodeEvent::ConnectPeer {
peer,
tx,
callback,
is_gw: transient,
} => {
tracing::debug!(
tx = %tx,
peer = %peer,
peer_addr = ?peer.socket_addr(),
transient,
phase = "connect",
"Received connect peer request"
);
ctx.handle_connect_peer(
peer,
Box::new(callback),
tx,
&handshake_cmd_sender,
&mut state,
transient,
)
.await?;
}
NodeEvent::ExpectPeerConnection { addr } => {
tracing::debug!(
peer_addr = %addr,
direction = "inbound",
phase = "connect",
"Expecting peer connection - registering with handshake handler"
);
state.expected_inbound.expect_incoming(addr);
let peer_placeholder = PeerKeyLocation::new(
(*ctx.bridge.op_manager.ring.connection_manager.pub_key)
.clone(),
addr,
);
if !handshake_cmd_sender.try_send(HandshakeCommand::ExpectInbound {
peer: peer_placeholder,
transaction: None,
transient: false,
}) {
tracing::warn!(
peer_addr = %addr,
phase = "connect",
"Handshake command channel full/closed; dropped \
ExpectInbound registration. Inbound connection will \
be accepted provisionally; connect op retries if the \
handshake does not complete."
);
}
}
NodeEvent::QueryConnections { callback } => {
let total_connections = ctx.connections.len();
let connections: Vec<PeerKeyLocation> = ctx
.connections
.iter()
.filter_map(|(addr, entry)| {
entry.pub_key.as_ref().map(|pub_key| {
PeerKeyLocation::new(pub_key.clone(), *addr)
})
})
.collect();
let with_pub_key = connections.len();
tracing::debug!(
total_connections,
with_pub_key,
phase = "query",
"Returning query connections result"
);
match timeout(
Duration::from_secs(1),
callback.send(QueryResult::Connections(connections)),
)
.await
{
Ok(Ok(())) => {}
Ok(Err(send_error)) => {
tracing::error!(
error = ?send_error,
phase = "error",
"Failed to send connections query result"
);
}
Err(elapsed) => {
tracing::error!(
error = ?elapsed,
phase = "timeout",
"Timeout sending connections query result"
);
}
}
}
NodeEvent::QuerySubscriptions { callback } => {
let network_subs = op_manager.get_network_subscriptions();
let network_subs: Vec<(
freenet_stdlib::prelude::ContractKey,
Vec<SocketAddr>,
)> = network_subs
.into_iter()
.map(|(key, peers)| {
let addrs = peers
.into_iter()
.filter_map(|p| p.socket_addr())
.collect();
(key, addrs)
})
.collect();
let app_subscriptions =
query_app_subscriptions_bounded(&op_manager.ch_outbound).await;
for (contract_key, peers) in &network_subs {
if !peers.is_empty() {
tracing::trace!(
contract = %contract_key,
subscriber_count = peers.len(),
phase = "query",
"Network subscription found"
);
}
}
let connections: Vec<PeerKeyLocation> = ctx
.connections
.iter()
.filter_map(|(addr, entry)| {
entry.pub_key.as_ref().map(|pub_key| {
PeerKeyLocation::new(pub_key.clone(), *addr)
})
})
.collect();
let debug_info = crate::message::NetworkDebugInfo {
application_subscriptions: app_subscriptions,
network_subscriptions: network_subs,
connected_peers: connections,
};
match timeout(
Duration::from_secs(1),
callback.send(QueryResult::NetworkDebug(debug_info)),
)
.await
{
Ok(Ok(())) => {}
Ok(Err(send_error)) => {
tracing::error!(
error = ?send_error,
phase = "error",
"Failed to send subscriptions query result"
);
}
Err(elapsed) => {
tracing::error!(
error = ?elapsed,
phase = "timeout",
"Timeout sending subscriptions query result"
);
}
}
}
NodeEvent::QueryNodeDiagnostics { config, callback } => {
use freenet_stdlib::client_api::{
NetworkInfo, NodeDiagnosticsResponse, NodeInfo, SystemMetrics,
};
use std::collections::HashMap;
let mut response = NodeDiagnosticsResponse {
node_info: None,
network_info: None,
subscriptions: Vec::new(),
contract_states: HashMap::new(),
system_metrics: None,
connected_peers_detailed: Vec::new(),
};
if config.include_node_info {
let addr = op_manager.ring.connection_manager.get_own_addr();
let location =
op_manager.ring.connection_manager.get_stored_location();
response.node_info = Some(NodeInfo {
peer_id: ctx.key_pair.public().to_string(),
is_gateway: self.is_gateway,
location: location.map(|loc| format!("{:.6}", loc.0)),
listening_address: addr
.map(|peer_addr| peer_addr.to_string()),
uptime_seconds: 0, });
}
if config.include_network_info {
let cm = &op_manager.ring.connection_manager;
let connections_by_loc = cm.get_connections_by_location();
let mut connected_peers = Vec::new();
for conns in connections_by_loc.values() {
for conn in conns {
connected_peers.push((
conn.location.pub_key().to_string(),
conn.location
.socket_addr()
.expect("connection should have address")
.to_string(),
));
}
}
connected_peers.sort_by(|a, b| a.0.cmp(&b.0));
connected_peers.dedup_by(|a, b| a.0 == b.0);
response.network_info = Some(NetworkInfo {
active_connections: connected_peers.len(),
connected_peers,
});
}
if config.include_subscriptions {
let _network_subs = op_manager.get_network_subscriptions();
response.subscriptions =
query_app_subscriptions_bounded(&op_manager.ch_outbound)
.await
.into_iter()
.map(|sub| {
freenet_stdlib::client_api::SubscriptionInfo {
contract_key: sub.instance_id,
client_id: sub.client_id.into(),
}
})
.collect();
}
response.contract_states = collect_contract_states(
op_manager.ring.as_ref(),
&config.contract_keys,
);
let cm = &op_manager.ring.connection_manager;
let connections_by_loc = cm.get_connections_by_location();
let mut connected_peer_ids = Vec::new();
if config.include_detailed_peer_info {
use freenet_stdlib::client_api::ConnectedPeerInfo;
for conns in connections_by_loc.values() {
for conn in conns {
connected_peer_ids
.push(conn.location.pub_key().to_string());
response.connected_peers_detailed.push(
ConnectedPeerInfo {
peer_id: conn.location.pub_key().to_string(),
address: conn
.location
.socket_addr()
.expect("connection should have address")
.to_string(),
},
);
}
}
} else {
for conns in connections_by_loc.values() {
connected_peer_ids.extend(
conns.iter().map(|c| c.location.pub_key().to_string()),
);
}
}
connected_peer_ids.sort();
connected_peer_ids.dedup();
if config.include_system_metrics {
let hosting_contracts =
op_manager.ring.hosting_contracts_count() as u32;
let pending_ops = op_manager.pending_op_counts();
let contract_waiters_count =
op_manager.contract_waiters_count();
let memory_rss_bytes =
crate::node::resource_metrics::rss_bytes();
tracing::info!(
pending_connect = pending_ops[0],
pending_put = pending_ops[1],
pending_get = pending_ops[2],
pending_subscribe = pending_ops[3],
pending_update = pending_ops[4],
contract_waiters = contract_waiters_count,
memory_rss_bytes = ?memory_rss_bytes,
"Node diagnostics: operation & memory metrics"
);
response.system_metrics = Some(SystemMetrics {
active_connections: connected_peer_ids.len() as u32,
hosting_contracts,
});
}
match timeout(
Duration::from_secs(2),
callback.send(QueryResult::NodeDiagnostics(response)),
)
.await
{
Ok(Ok(())) => {}
Ok(Err(send_error)) => {
tracing::error!(
error = ?send_error,
phase = "error",
"Failed to send node diagnostics query result"
);
}
Err(elapsed) => {
tracing::error!(
error = ?elapsed,
phase = "timeout",
"Timeout sending node diagnostics query result"
);
}
}
}
NodeEvent::TransactionTimedOut(tx) => {
if let Some(clients) = state.tx_to_client.remove(&tx) {
tracing::debug!(
tx = %tx,
client_count = clients.len(),
phase = "timeout",
"Cleaned up client subscriptions for timed out transaction"
);
}
if state.pending_op_results.remove(&tx).is_some() {
crate::config::GlobalTestMetrics::record_pending_op_remove();
}
}
NodeEvent::TransactionCompleted(tx) => {
state.tx_to_client.remove(&tx);
if state.pending_op_results.remove(&tx).is_some() {
crate::config::GlobalTestMetrics::record_pending_op_remove();
}
ctx.bridge
.op_manager
.ring
.live_tx_tracker
.remove_finished_transaction(tx);
}
NodeEvent::TransactionOrphaned { tx, peer } => {
state.tx_to_client.remove(&tx);
if let Some(sender) = state.pending_op_results.remove(&tx) {
#[allow(clippy::let_underscore_must_use)]
let _ = sender.try_send(WaiterReply::PeerDisconnected { peer });
crate::config::GlobalTestMetrics::record_pending_op_remove();
}
ctx.bridge
.op_manager
.ring
.live_tx_tracker
.remove_finished_transaction(tx);
}
NodeEvent::LocalSubscribeComplete {
tx,
key,
subscribed,
is_renewal,
} => {
tracing::debug!(
tx = %tx,
contract = %key,
is_renewal,
phase = "complete",
"Standalone subscribe operation completed"
);
if tx.is_sub_operation() {
tracing::debug!(
tx = %tx,
contract = %key,
phase = "complete",
"Completing standalone child subscribe operation"
);
op_manager.completed(tx);
continue;
}
if is_renewal {
tracing::debug!(
tx = %tx,
contract = %key,
phase = "complete",
"Skipping client notification for standalone subscription renewal"
);
op_manager.completed(tx);
continue;
}
let response = Ok(HostResponse::ContractResponse(
ContractResponse::SubscribeResponse { key, subscribed },
));
match op_manager.result_router_tx.try_send((tx, response)) {
Ok(()) => {
tracing::debug!(
tx = %tx,
phase = "response",
"Sent standalone subscribe response to client"
);
if let Some(clients) = state.tx_to_client.remove(&tx) {
tracing::trace!(
tx = %tx,
client_count = clients.len(),
"Removed waiting clients for completed transaction"
);
} else if let Some(pos) = state
.client_waiting_transaction
.iter()
.position(|(waiting, _)| match waiting {
WaitingTransaction::Subscription {
contract_key,
} => contract_key == key.id(),
_ => false,
})
{
let (_, clients) =
state.client_waiting_transaction.remove(pos);
tracing::trace!(
tx = %tx,
contract = %key,
waiter_count = clients.len(),
"Matched subscription waiters by contract"
);
} else {
tracing::warn!(
tx = %tx,
phase = "complete",
"Standalone subscribe complete but no waiting clients found"
);
}
}
Err(e) => {
tracing::error!(
tx = %tx,
error = %e,
phase = "error",
"Failed to send standalone subscribe response to client \
(result router channel full or closed)"
);
}
}
}
NodeEvent::BroadcastHostingUpdate { message } => {
ctx.handle_hosting_broadcast(message).await;
}
NodeEvent::BroadcastChangeInterests { added, removed } => {
ctx.handle_broadcast_change_interests(added, removed).await;
}
NodeEvent::SendInterestMessage { target, message } => {
ctx.handle_send_interest_message(target, message).await;
}
NodeEvent::SendNetMessage { target, msg } => {
ctx.handle_send_net_message(target, *msg).await;
}
NodeEvent::Disconnect { cause } => {
tracing::info!(
cause = ?cause,
phase = "shutdown",
"Disconnecting from network"
);
graceful_shutdown = true;
break;
}
NodeEvent::BroadcastStateChange {
key,
new_state,
is_retry,
is_reemit,
} => {
ctx.handle_broadcast_state_change(
&op_manager,
key,
new_state,
is_retry,
is_reemit,
)
.await;
}
NodeEvent::SyncStateToPeer {
key,
new_state,
target,
} => {
ctx.handle_sync_state_to_peer(&op_manager, key, new_state, target)
.await;
}
NodeEvent::ConsiderContractMigration { key } => {
ctx.consider_contract_migration(key);
}
},
}
}
}
}
if graceful_shutdown {
Err(EventLoopExitReason::GracefulShutdown.into())
} else {
Err(EventLoopExitReason::UnexpectedStreamEnd.into())
}
}
}
trait ConnectResultSender: Send {
fn send_result(
&mut self,
result: Result<(SocketAddr, Option<usize>), ()>,
) -> Pin<Box<dyn Future<Output = Result<(), ()>> + Send + '_>>;
}
impl ConnectResultSender for mpsc::Sender<Result<(SocketAddr, Option<usize>), ()>> {
fn send_result(
&mut self,
result: Result<(SocketAddr, Option<usize>), ()>,
) -> Pin<Box<dyn Future<Output = Result<(), ()>> + Send + '_>> {
async move { self.send(result).await.map_err(|_| ()) }.boxed()
}
}
async fn fail_awaiting_connection(
state: &mut EventListenerState,
peer_addr: SocketAddr,
tx: Transaction,
transient: bool,
) {
let pending_txs = state.awaiting_connection_txs.remove(&peer_addr);
if let Some(callbacks) = state.awaiting_connection.remove(&peer_addr) {
tracing::debug!(
tx = %tx,
remote = %peer_addr,
callbacks = callbacks.len(),
transient,
"Cleaning up callbacks after connect command failure"
);
for mut cb in callbacks {
cb.send_result(Err(()))
.await
.inspect_err(|send_err| {
tracing::debug!(
remote = %peer_addr,
?send_err,
"Failed to deliver connect command failure to awaiting callback"
);
})
.ok();
}
}
if let Some(pending_txs) = pending_txs {
tracing::debug!(
remote = %peer_addr,
pending_txs = ?pending_txs,
"Removed pending transactions after connect command failure"
);
}
}
struct EventListenerState {
expected_inbound: ExpectedInboundTracker,
pending_from_executor: HashSet<Transaction>,
tx_to_client: HashMap<Transaction, HashSet<ClientId>>,
client_waiting_transaction: Vec<(WaitingTransaction, HashSet<ClientId>)>,
awaiting_connection: HashMap<SocketAddr, Vec<Box<dyn ConnectResultSender>>>,
awaiting_connection_txs: HashMap<SocketAddr, Vec<Transaction>>,
pending_op_results: HashMap<Transaction, Sender<WaiterReply>>,
last_pending_op_cleanup: Instant,
peer_backoff: PeerConnectionBackoff,
last_backoff_cleanup: Instant,
}
impl EventListenerState {
fn new(expected_inbound: ExpectedInboundTracker) -> Self {
Self {
expected_inbound,
pending_from_executor: HashSet::new(),
tx_to_client: HashMap::new(),
client_waiting_transaction: Vec::new(),
awaiting_connection: HashMap::new(),
pending_op_results: HashMap::new(),
last_pending_op_cleanup: Instant::now(),
awaiting_connection_txs: HashMap::new(),
peer_backoff: PeerConnectionBackoff::new(),
last_backoff_cleanup: Instant::now(),
}
}
}
impl Drop for EventListenerState {
fn drop(&mut self) {
for _ in 0..self.pending_op_results.len() {
crate::config::GlobalTestMetrics::record_pending_op_remove();
}
}
}
enum EventResult {
Continue,
Event(Box<ConnEvent>),
}
#[derive(Debug)]
pub(super) enum ConnEvent {
InboundMessage(IncomingMessage),
OutboundMessage(NetMessage),
OutboundMessageWithTarget {
target_addr: SocketAddr,
msg: NetMessage,
},
NodeAction(NodeEvent),
ClosedChannel(ChannelCloseReason),
TransportClosed {
remote_addr: SocketAddr,
error: TransportError,
connection_id: u64,
},
StreamSend {
target_addr: SocketAddr,
stream_id: StreamId,
data: bytes::Bytes,
metadata: Option<bytes::Bytes>,
completion_tx: Option<tokio::sync::oneshot::Sender<BroadcastDeliveryOutcome>>,
progress: Option<crate::operations::stream_progress::StreamProgressHandle>,
},
PipeStream {
target_addr: SocketAddr,
outbound_stream_id: StreamId,
inbound_handle: crate::transport::peer_connection::streaming::StreamHandle,
metadata: Option<bytes::Bytes>,
},
}
#[derive(Debug)]
pub(super) struct IncomingMessage {
pub msg: NetMessage,
pub remote_addr: Option<SocketAddr>,
}
impl IncomingMessage {
fn with_remote(msg: NetMessage, remote_addr: SocketAddr) -> Self {
Self {
msg,
remote_addr: Some(remote_addr),
}
}
}
impl From<NetMessage> for IncomingMessage {
fn from(msg: NetMessage) -> Self {
Self {
msg,
remote_addr: None,
}
}
}
#[derive(Debug)]
pub(super) enum ChannelCloseReason {
Bridge,
Controller,
Notification,
OpExecution,
}
#[allow(dead_code)]
enum ProtocolStatus {
Unconfirmed,
Confirmed,
Reported,
Failed,
}
async fn handle_peer_channel_message(
conn: &mut Box<dyn PeerConnectionApi>,
msg: Either<NetMessage, ConnEvent>,
) -> Result<(), TransportError> {
match msg {
Left(msg) => {
tracing::debug!(to=%conn.remote_addr() ,"Sending message to peer. Msg: {msg}");
if let Err(error) = conn.send_message(msg).await {
tracing::error!(
to = %conn.remote_addr(),
?error,
"[CONN_LIFECYCLE] Failed to send message to peer"
);
return Err(error);
}
tracing::debug!(
to = %conn.remote_addr(),
"[CONN_LIFECYCLE] Message enqueued on transport socket"
);
}
Right(action) => {
tracing::debug!(to=%conn.remote_addr(), "Received action from channel");
match action {
ConnEvent::NodeAction(NodeEvent::DropConnection(peer)) => {
tracing::info!(
to = %conn.remote_addr(),
peer = %peer,
"[CONN_LIFECYCLE] Closing connection per DropConnection action"
);
return Err(TransportError::ConnectionClosed(conn.remote_addr()));
}
ConnEvent::ClosedChannel(reason) => {
tracing::info!(
to = %conn.remote_addr(),
reason = ?reason,
"[CONN_LIFECYCLE] Closing connection due to ClosedChannel action"
);
return Err(TransportError::ConnectionClosed(conn.remote_addr()));
}
ConnEvent::StreamSend {
stream_id,
data,
metadata,
completion_tx,
progress,
..
} => {
tracing::debug!(
to = %conn.remote_addr(),
stream_id = %stream_id,
data_len = data.len(),
has_metadata = metadata.is_some(),
"[CONN_LIFECYCLE] Sending operations stream data to peer"
);
if let Err(error) = conn
.send_stream_data(stream_id, data, metadata, completion_tx, progress)
.await
{
tracing::error!(
to = %conn.remote_addr(),
stream_id = %stream_id,
?error,
"[CONN_LIFECYCLE] Failed to send stream data to peer"
);
return Err(error);
}
}
ConnEvent::PipeStream {
outbound_stream_id,
inbound_handle,
metadata,
..
} => {
tracing::debug!(
to = %conn.remote_addr(),
stream_id = %outbound_stream_id,
total_bytes = inbound_handle.total_bytes(),
has_metadata = metadata.is_some(),
"[CONN_LIFECYCLE] Piping stream to peer"
);
if let Err(error) = conn
.pipe_stream_data(outbound_stream_id, inbound_handle, metadata, None)
.await
{
tracing::error!(
to = %conn.remote_addr(),
stream_id = %outbound_stream_id,
?error,
"[CONN_LIFECYCLE] Failed to pipe stream to peer"
);
return Err(error);
}
}
other @ ConnEvent::InboundMessage(_)
| other @ ConnEvent::OutboundMessage(_)
| other @ ConnEvent::OutboundMessageWithTarget { .. }
| other @ ConnEvent::NodeAction(_)
| other @ ConnEvent::TransportClosed { .. } => {
unreachable!(
"Unexpected action from peer_connection_listener channel: {:?}",
other
);
}
}
}
}
Ok(())
}
async fn notify_transport_closed(
sender: &Sender<ConnEvent>,
remote_addr: SocketAddr,
error: TransportError,
connection_id: u64,
) {
if sender
.send(ConnEvent::TransportClosed {
remote_addr,
error,
connection_id,
})
.await
.is_err()
{
tracing::debug!(
remote = %remote_addr,
"[CONN_LIFECYCLE] conn_events receiver dropped before handling closure event"
);
}
}
async fn drain_pending_before_shutdown(
rx: &mut PeerConnChannelRecv,
conn: &mut Box<dyn PeerConnectionApi>,
remote_addr: SocketAddr,
) -> usize {
let mut drained = 0;
while let Ok(msg) = rx.try_recv() {
if let Err(e) = handle_peer_channel_message(conn, msg).await {
tracing::debug!(
to = %remote_addr,
?e,
drained,
"[CONN_LIFECYCLE] Error during shutdown drain (expected if connection closed)"
);
break;
}
drained += 1;
}
if drained > 0 {
tracing::info!(
to = %remote_addr,
drained,
"[CONN_LIFECYCLE] Drained pending messages before shutdown"
);
}
drained
}
async fn peer_connection_listener(
mut rx: PeerConnChannelRecv,
mut conn: Box<dyn PeerConnectionApi>,
peer_addr: SocketAddr,
conn_events: Sender<ConnEvent>,
connection_id: u64,
) {
let remote_addr = conn.remote_addr();
tracing::debug!(
to = %remote_addr,
%peer_addr,
"[CONN_LIFECYCLE] Starting peer_connection_listener task"
);
loop {
tokio::task::yield_now().await;
const MAX_OUTBOUND_DRAIN: usize = 8;
let mut drained = 0;
loop {
match rx.try_recv() {
Ok(msg) => {
drained += 1;
if let Err(error) = handle_peer_channel_message(&mut conn, msg).await {
if error.is_transient_send_failure() {
tracing::warn!(
to = %remote_addr,
?error,
"[CONN_LIFECYCLE] Transient send failure, continuing"
);
} else {
tracing::debug!(
to = %remote_addr,
?error,
"[CONN_LIFECYCLE] Connection closed after channel command"
);
drain_pending_before_shutdown(&mut rx, &mut conn, remote_addr).await;
notify_transport_closed(
&conn_events,
remote_addr,
error,
connection_id,
)
.await;
return;
}
}
if drained >= MAX_OUTBOUND_DRAIN {
break;
}
}
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
tracing::warn!(
to = %remote_addr,
"[CONN_LIFECYCLE] peer_connection_listener channel closed without explicit DropConnection"
);
notify_transport_closed(
&conn_events,
remote_addr,
TransportError::ConnectionClosed(remote_addr),
connection_id,
)
.await;
return;
}
}
}
crate::deterministic_select! {
msg = rx.recv() => {
match msg {
Some(msg) => {
if let Err(error) = handle_peer_channel_message(&mut conn, msg).await {
if error.is_transient_send_failure() {
tracing::warn!(
to = %remote_addr,
?error,
"[CONN_LIFECYCLE] Transient send failure, continuing"
);
} else {
tracing::debug!(
to = %remote_addr,
?error,
"[CONN_LIFECYCLE] Connection closed after channel command"
);
drain_pending_before_shutdown(&mut rx, &mut conn, remote_addr).await;
notify_transport_closed(&conn_events, remote_addr, error, connection_id).await;
return;
}
}
}
None => {
tracing::warn!(
to = %remote_addr,
"[CONN_LIFECYCLE] peer_connection_listener channel closed without explicit DropConnection"
);
notify_transport_closed(
&conn_events,
remote_addr,
TransportError::ConnectionClosed(remote_addr),
connection_id,
)
.await;
return;
}
}
},
msg = conn.recv() => {
match msg {
Ok(msg) => match decode_msg(&msg) {
Ok(net_message) => {
let tx = *net_message.id();
tracing::debug!(
from = %remote_addr,
%tx,
tx_type = ?tx.transaction_type(),
msg_type = %net_message,
"[CONN_LIFECYCLE] Received inbound NetMessage from peer"
);
if conn_events
.send(ConnEvent::InboundMessage(IncomingMessage::with_remote(
net_message,
remote_addr,
)))
.await
.is_err()
{
tracing::debug!(
from = %remote_addr,
"[CONN_LIFECYCLE] conn_events receiver dropped; stopping listener"
);
drain_pending_before_shutdown(&mut rx, &mut conn, remote_addr).await;
return;
}
}
Err(error) => {
tracing::error!(
from = %remote_addr,
?error,
"[CONN_LIFECYCLE] Failed to deserialize inbound message; closing connection"
);
drain_pending_before_shutdown(&mut rx, &mut conn, remote_addr).await;
let transport_error = TransportError::Other(anyhow!(
"Failed to deserialize inbound message from {remote_addr}: {error:?}"
));
notify_transport_closed(&conn_events, remote_addr, transport_error, connection_id).await;
return;
}
},
Err(error) if error.is_transient_send_failure() => {
tracing::warn!(
from = %remote_addr,
?error,
"[CONN_LIFECYCLE] Transient send failure during recv, continuing"
);
}
Err(error) => {
tracing::debug!(
from = %remote_addr,
?error,
"[CONN_LIFECYCLE] peer_connection_listener terminating after recv error"
);
drain_pending_before_shutdown(&mut rx, &mut conn, remote_addr).await;
notify_transport_closed(&conn_events, remote_addr, error, connection_id).await;
return;
}
}
},
}
}
}
#[inline(always)]
fn decode_msg(data: &[u8]) -> Result<NetMessage, ConnectionError> {
bincode::deserialize(data).map_err(|err| ConnectionError::Serialization(Some(err)))
}
fn extract_sender_from_message(msg: &NetMessage) -> Option<PeerKeyLocation> {
match msg {
NetMessage::V1(msg_v1) => match msg_v1 {
NetMessageV1::Connect(ConnectMsg::Request { payload, .. }) => {
Some(payload.joiner.clone())
}
NetMessageV1::Connect(_)
| NetMessageV1::Get(_)
| NetMessageV1::Put(_)
| NetMessageV1::Update(_)
| NetMessageV1::Subscribe(_) => None,
NetMessageV1::Aborted(_)
| NetMessageV1::NeighborHosting { .. }
| NetMessageV1::InterestSync { .. }
| NetMessageV1::ReadyState { .. }
| NetMessageV1::SubscribeHint(_) => None,
},
}
}
fn extract_sender_from_message_mut(msg: &mut NetMessage) -> Option<&mut PeerKeyLocation> {
match msg {
NetMessage::V1(msg_v1) => match msg_v1 {
NetMessageV1::Connect(_)
| NetMessageV1::Get(_)
| NetMessageV1::Put(_)
| NetMessageV1::Update(_)
| NetMessageV1::Subscribe(_) => None,
NetMessageV1::Aborted(_)
| NetMessageV1::NeighborHosting { .. }
| NetMessageV1::InterestSync { .. }
| NetMessageV1::ReadyState { .. }
| NetMessageV1::SubscribeHint(_) => None,
},
}
}
#[cfg(test)]
mod tests {
use crate::config::GlobalExecutor;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::mpsc;
use tokio::time::{Duration, Instant, sleep, timeout};
struct FakePresence {
hosting: std::collections::HashSet<freenet_stdlib::prelude::ContractKey>,
subscribed: std::collections::HashSet<freenet_stdlib::prelude::ContractKey>,
}
impl super::ContractPresenceOracle for FakePresence {
fn is_hosting_contract(&self, key: &freenet_stdlib::prelude::ContractKey) -> bool {
self.hosting.contains(key)
}
fn is_subscribed(&self, key: &freenet_stdlib::prelude::ContractKey) -> bool {
self.subscribed.contains(key)
}
fn hosting_contract_keys(&self) -> Vec<freenet_stdlib::prelude::ContractKey> {
self.hosting.iter().copied().collect()
}
fn hosting_contract_size(&self, _key: &freenet_stdlib::prelude::ContractKey) -> u64 {
42
}
}
fn contract_key_from_seed(seed: u8) -> freenet_stdlib::prelude::ContractKey {
use freenet_stdlib::prelude::{CodeHash, ContractInstanceId, ContractKey};
let mut bytes = [0u8; 32];
bytes[0] = seed;
ContractKey::from_id_and_code(ContractInstanceId::new(bytes), CodeHash::new([0u8; 32]))
}
#[test]
fn collect_contract_states_filters_unknown_explicit_keys() {
let hosted = contract_key_from_seed(0x01);
let subscribed = contract_key_from_seed(0x02);
let unknown = contract_key_from_seed(0x03);
let oracle = FakePresence {
hosting: std::iter::once(hosted).collect(),
subscribed: std::iter::once(subscribed).collect(),
};
let states = super::collect_contract_states(&oracle, &[hosted, subscribed, unknown]);
assert!(
states.contains_key(&hosted.to_string()),
"a hosted key must be reported as present (positive control)"
);
assert!(
states.contains_key(&subscribed.to_string()),
"a subscribed key must be reported as present (positive control)"
);
assert!(
!states.contains_key(&unknown.to_string()),
"a key the node neither hosts nor subscribes to MUST be omitted — \
reporting it would defeat the #3945 local-presence gate"
);
assert_eq!(
states.len(),
2,
"only the two locally-present keys may appear in contract_states"
);
}
#[test]
fn collect_contract_states_empty_query_returns_all_hosting() {
let a = contract_key_from_seed(0x11);
let b = contract_key_from_seed(0x12);
let oracle = FakePresence {
hosting: [a, b].into_iter().collect(),
subscribed: std::collections::HashSet::new(),
};
let states = super::collect_contract_states(&oracle, &[]);
assert_eq!(
states.len(),
2,
"empty query must dump all hosting contracts"
);
assert!(states.contains_key(&a.to_string()));
assert!(states.contains_key(&b.to_string()));
}
mod version_gate {
use super::super::{SUBSCRIBE_HINT_MIN_VERSION, version_supports_subscribe_hint};
const FLOOR: (u8, u8, u16) = (0, 2, 73);
#[test]
fn unknown_remote_version_fails_closed() {
assert!(!version_supports_subscribe_hint(None, FLOOR));
}
#[test]
fn older_remote_version_is_rejected() {
assert!(!version_supports_subscribe_hint(Some((0, 2, 72)), FLOOR));
assert!(!version_supports_subscribe_hint(Some((0, 1, 99)), FLOOR));
}
#[test]
fn equal_or_newer_remote_version_is_accepted() {
assert!(version_supports_subscribe_hint(Some((0, 2, 73)), FLOOR));
assert!(version_supports_subscribe_hint(Some((0, 2, 74)), FLOOR));
assert!(version_supports_subscribe_hint(Some((0, 3, 0)), FLOOR));
assert!(version_supports_subscribe_hint(Some((1, 0, 0)), FLOOR));
}
#[test]
fn tuple_ordering_is_major_then_minor_then_patch() {
assert!(version_supports_subscribe_hint(Some((0, 3, 0)), FLOOR));
assert!(version_supports_subscribe_hint(Some((0, 0, 0)), (0, 0, 0)));
assert!(!version_supports_subscribe_hint(None, (0, 0, 0)));
}
#[ignore]
#[test]
fn placement_migration_deactivated_for_all_0_2_x() {
assert!(!version_supports_subscribe_hint(
Some((0, 2, 73)),
SUBSCRIBE_HINT_MIN_VERSION
));
assert!(!version_supports_subscribe_hint(
Some((0, 2, 255)),
SUBSCRIBE_HINT_MIN_VERSION
));
assert!(!version_supports_subscribe_hint(
None,
SUBSCRIBE_HINT_MIN_VERSION
));
}
#[test]
fn subscribe_hint_wire_floor_frozen_at_0_2_80() {
assert!(version_supports_subscribe_hint(
Some((0, 2, 80)),
SUBSCRIBE_HINT_MIN_VERSION
));
assert!(version_supports_subscribe_hint(
Some((0, 2, 255)),
SUBSCRIBE_HINT_MIN_VERSION
));
assert!(!version_supports_subscribe_hint(
Some((0, 2, 79)),
SUBSCRIBE_HINT_MIN_VERSION
));
assert!(!version_supports_subscribe_hint(
Some((0, 2, 73)),
SUBSCRIBE_HINT_MIN_VERSION
));
assert!(!version_supports_subscribe_hint(
None,
SUBSCRIBE_HINT_MIN_VERSION
));
}
}
mod migration_selection {
use super::super::pick_closest_migration_target;
use crate::ring::{Location, PeerKeyLocation};
use crate::transport::TransportPublicKey;
use std::collections::HashSet;
use std::net::SocketAddr;
fn pkl(pk_byte: u8, addr: &str) -> PeerKeyLocation {
let pub_key = TransportPublicKey::from_bytes([pk_byte; 32]);
let addr: SocketAddr = addr.parse().expect("valid socket addr");
PeerKeyLocation::new(pub_key, addr)
}
fn dist_to(contract: Location, p: &PeerKeyLocation) -> crate::ring::Distance {
contract.distance(p.location().expect("known location"))
}
#[test]
fn picks_strictly_closest_non_hosting_candidate() {
let contract = Location::new(0.5);
let a = pkl(1, "10.0.0.1:1000");
let b = pkl(2, "10.0.0.2:1000");
let c = pkl(3, "10.0.0.3:1000");
let candidates = [a.clone(), b.clone(), c.clone()];
let closest = candidates
.iter()
.min_by_key(|p| dist_to(contract, p))
.unwrap()
.clone();
let my_dist = candidates
.iter()
.map(|p| dist_to(contract, p))
.max()
.unwrap();
let my_dist = crate::ring::Distance::new(my_dist.as_f64() + 0.01);
let hosting = HashSet::new();
let chosen = pick_closest_migration_target(contract, my_dist, &hosting, &candidates)
.expect("a qualifying candidate exists");
assert_eq!(
chosen.pub_key(),
closest.pub_key(),
"must pick the candidate closest to the contract location"
);
}
#[test]
fn excludes_candidates_not_closer_than_us() {
let contract = Location::new(0.5);
let a = pkl(1, "10.0.0.1:1000");
let candidates = [a.clone()];
let my_dist = crate::ring::Distance::new(dist_to(contract, &a).as_f64() - 0.0001);
let hosting = HashSet::new();
assert!(
pick_closest_migration_target(contract, my_dist, &hosting, &candidates).is_none(),
"a candidate no closer than us must be excluded"
);
}
#[test]
fn excludes_already_hosting_candidates() {
let contract = Location::new(0.5);
let a = pkl(1, "10.0.0.1:1000");
let candidates = [a.clone()];
let my_dist = crate::ring::Distance::new(dist_to(contract, &a).as_f64() + 0.01);
let mut hosting = HashSet::new();
hosting.insert(a.pub_key().clone());
assert!(
pick_closest_migration_target(contract, my_dist, &hosting, &candidates).is_none(),
"a candidate already hosting the contract must be excluded"
);
}
#[test]
fn excludes_unknown_address_candidates() {
let contract = Location::new(0.5);
let unknown =
PeerKeyLocation::with_unknown_addr(TransportPublicKey::from_bytes([7; 32]));
let candidates = [unknown];
let my_dist = crate::ring::Distance::new(1.0);
let hosting = HashSet::new();
assert!(
pick_closest_migration_target(contract, my_dist, &hosting, &candidates).is_none(),
"a candidate without a known socket address must be excluded"
);
}
#[test]
fn breaks_ties_deterministically_by_pubkey() {
let contract = Location::new(0.5);
let low = pkl(1, "10.0.0.9:1000");
let high = pkl(9, "10.0.0.9:1000");
assert_eq!(
dist_to(contract, &low),
dist_to(contract, &high),
"test setup: both candidates must be equidistant"
);
let my_dist = crate::ring::Distance::new(dist_to(contract, &low).as_f64() + 0.01);
let hosting = HashSet::new();
let forward = [low.clone(), high.clone()];
let reverse = [high.clone(), low.clone()];
let chosen_fwd =
pick_closest_migration_target(contract, my_dist, &hosting, &forward).unwrap();
let chosen_rev =
pick_closest_migration_target(contract, my_dist, &hosting, &reverse).unwrap();
assert_eq!(chosen_fwd.pub_key(), low.pub_key());
assert_eq!(chosen_rev.pub_key(), low.pub_key());
}
#[test]
fn returns_none_when_no_candidates() {
let contract = Location::new(0.5);
let candidates: [PeerKeyLocation; 0] = [];
let hosting = HashSet::new();
assert!(
pick_closest_migration_target(
contract,
crate::ring::Distance::new(1.0),
&hosting,
&candidates
)
.is_none()
);
}
}
#[tokio::test]
async fn test_drain_before_shutdown_prevents_message_loss() {
let (tx, mut rx) = mpsc::channel::<String>(10);
let processed = Arc::new(AtomicUsize::new(0));
let processed_clone = processed.clone();
let listener = GlobalExecutor::spawn(async move {
loop {
loop {
match rx.try_recv() {
Ok(msg) => {
tracing::debug!("Drained message: {}", msg);
processed_clone.fetch_add(1, Ordering::SeqCst);
}
Err(mpsc::error::TryRecvError::Empty) => break,
Err(mpsc::error::TryRecvError::Disconnected) => return,
}
}
tokio::select! {
msg = rx.recv() => {
match msg {
Some(m) => {
tracing::debug!("Received via select: {}", m);
processed_clone.fetch_add(1, Ordering::SeqCst);
}
None => return, }
}
_ = sleep(Duration::from_millis(50)) => {
tracing::debug!("Simulated error occurred");
let mut drained = 0;
while let Ok(msg) = rx.try_recv() {
tracing::debug!("Drain before shutdown: {}", msg);
processed_clone.fetch_add(1, Ordering::SeqCst);
drained += 1;
}
tracing::debug!("Drained {} messages before shutdown", drained);
return;
}
}
}
});
GlobalExecutor::spawn(async move {
sleep(Duration::from_millis(10)).await;
for i in 0..5 {
tx.send(format!("msg{}", i)).await.unwrap();
sleep(Duration::from_millis(5)).await;
}
});
let result = timeout(Duration::from_millis(200), listener).await;
assert!(result.is_ok(), "Listener should complete");
let count = processed.load(Ordering::SeqCst);
assert_eq!(
count, 5,
"All messages should be processed (drained before shutdown). Got {} instead of 5. \
If this fails, messages are being lost during the error-exit drain.",
count
);
}
#[tokio::test]
async fn test_message_loss_without_drain() {
let (tx, mut rx) = mpsc::channel::<String>(10);
let processed = Arc::new(AtomicUsize::new(0));
let processed_clone = processed.clone();
let (ready_tx, ready_rx) = tokio::sync::oneshot::channel::<()>();
let listener = GlobalExecutor::spawn(async move {
loop {
match rx.try_recv() {
Ok(msg) => {
tracing::debug!("Drained message: {}", msg);
processed_clone.fetch_add(1, Ordering::SeqCst);
}
Err(mpsc::error::TryRecvError::Empty) => break,
Err(mpsc::error::TryRecvError::Disconnected) => return,
}
}
assert!(ready_tx.send(()).is_ok(), "ready_tx receiver dropped");
tokio::select! {
biased; _ = sleep(Duration::from_millis(1)) => {
tracing::debug!("Error occurred - BUG: returning without drain!");
}
msg = rx.recv() => {
if let Some(m) = msg {
tracing::debug!("Received via select: {}", m);
processed_clone.fetch_add(1, Ordering::SeqCst);
}
}
}
});
let _ready = ready_rx.await;
for i in 0..5 {
let _sent = tx.send(format!("msg{}", i)).await;
}
let _listener_result = timeout(Duration::from_millis(100), listener).await;
let count = processed.load(Ordering::SeqCst);
assert!(
count < 5,
"Without drain, messages should be lost. Got {} (expected < 5). \
This test validates the bug exists when drain is missing.",
count
);
}
#[test]
fn event_listener_state_drop_records_remaining_pending_op_removes() {
use crate::config::GlobalTestMetrics;
use crate::message::Transaction;
use crate::transport::ExpectedInboundTracker;
let removes_before = GlobalTestMetrics::pending_op_removes();
let mut state = super::EventListenerState::new(ExpectedInboundTracker::empty_for_test());
for _ in 0..3 {
let (callback, _rx) = mpsc::channel(1);
state
.pending_op_results
.insert(Transaction::ttl_transaction(), callback);
}
drop(state);
assert_eq!(
GlobalTestMetrics::pending_op_removes() - removes_before,
3,
"Drop must record one pending_op_remove per resident entry"
);
}
#[test]
fn handle_orphaned_transactions_wakes_parked_drivers() {
let src = include_str!("p2p_protoc.rs");
let handler_start = src
.find("pub(crate) async fn handle_orphaned_transactions(")
.expect("handle_orphaned_transactions must exist");
let body_after = &src[handler_start..];
let end = body_after
.find("\n }\n")
.expect("handle_orphaned_transactions must close cleanly");
let body = &body_after[..end];
assert!(
body.contains("notify_orphaned_transaction"),
"handle_orphaned_transactions must call \
notify_orphaned_transaction to wake parked drivers (#4154/#4313). \
Found body:\n{body}"
);
}
#[test]
fn event_listener_state_drop_empty_records_nothing() {
use crate::config::GlobalTestMetrics;
use crate::transport::ExpectedInboundTracker;
let removes_before = GlobalTestMetrics::pending_op_removes();
drop(super::EventListenerState::new(
ExpectedInboundTracker::empty_for_test(),
));
assert_eq!(GlobalTestMetrics::pending_op_removes(), removes_before);
}
#[test]
fn test_connection_id_uniqueness() {
let id1 = super::next_connection_id();
let id2 = super::next_connection_id();
let id3 = super::next_connection_id();
assert!(id2 > id1, "IDs must be monotonically increasing");
assert!(id3 > id2, "IDs must be monotonically increasing");
}
#[test]
fn test_stale_transport_closed_detection() {
use std::collections::BTreeMap;
use std::net::SocketAddr;
let addr: SocketAddr = "1.2.3.4:5678".parse().unwrap();
let mut connections: BTreeMap<SocketAddr, super::ConnectionEntry> = BTreeMap::new();
let (tx1, _rx1) = mpsc::channel(1);
connections.insert(
addr,
super::ConnectionEntry {
sender: tx1,
pub_key: None,
connection_id: 10,
created_at: Instant::now(),
remote_version: None,
},
);
let (tx2, _rx2) = mpsc::channel(1);
connections.insert(
addr,
super::ConnectionEntry {
sender: tx2,
pub_key: None,
connection_id: 20,
created_at: Instant::now(),
remote_version: None,
},
);
let current = connections.get(&addr).unwrap();
assert_ne!(current.connection_id, 10, "stale event must not match");
assert_eq!(current.connection_id, 20);
assert_eq!(current.connection_id, 20, "current event must match");
connections.remove(&addr);
assert!(!connections.contains_key(&addr));
}
const TEST_TRANSIENT_TTL: Duration = Duration::from_secs(30);
#[test]
fn test_zombie_detection_ignores_young_connections() {
let elapsed = Duration::from_secs(60);
assert!(
!super::is_zombie(elapsed, false, false, false, TEST_TRANSIENT_TTL),
"Young connection should not be zombie"
);
}
#[test]
fn test_zombie_detection_ignores_ring_connections() {
let elapsed = Duration::from_secs(400);
assert!(
!super::is_zombie(elapsed, true, false, false, TEST_TRANSIENT_TTL),
"Ring connection should not be zombie"
);
}
#[test]
fn test_zombie_detection_catches_old_unpromoted() {
let elapsed = Duration::from_secs(91); assert!(
super::is_zombie(elapsed, false, false, false, TEST_TRANSIENT_TTL),
"Old unpromoted connection should be zombie"
);
}
#[test]
fn test_zombie_detection_ignores_pending_reservation() {
let elapsed = Duration::from_secs(120); assert!(
!super::is_zombie(elapsed, false, true, false, TEST_TRANSIENT_TTL),
"Connection with pending reservation below absolute threshold should not be zombie"
);
}
#[test]
fn test_zombie_absolute_threshold_overrides_pending() {
let elapsed = Duration::from_secs(181);
assert!(
super::is_zombie(elapsed, false, true, false, TEST_TRANSIENT_TTL),
"Absolute threshold should override has_pending"
);
}
#[test]
fn test_zombie_absolute_threshold_spares_ring() {
let elapsed = Duration::from_secs(181);
assert!(
!super::is_zombie(elapsed, true, true, false, TEST_TRANSIENT_TTL),
"Ring connection should never be zombie even past absolute threshold"
);
}
#[test]
fn test_zombie_boundary_exactly_at_threshold() {
assert!(!super::is_zombie(
Duration::from_secs(90),
false,
false,
false,
TEST_TRANSIENT_TTL
));
}
#[test]
fn test_zombie_boundary_exactly_at_absolute() {
assert!(!super::is_zombie(
Duration::from_secs(180),
false,
true,
false,
TEST_TRANSIENT_TTL
));
}
#[test]
fn test_zombie_detection_ignores_gateway_connections() {
let elapsed = Duration::from_secs(400); assert!(
!super::is_zombie(elapsed, false, false, true, TEST_TRANSIENT_TTL),
"Gateway connection should not be zombie within exemption window"
);
let stale = Duration::from_secs(3601);
assert!(
super::is_zombie(stale, false, false, true, TEST_TRANSIENT_TTL),
"Gateway connection past 1-hour cap should be zombie"
);
}
#[test]
fn test_zombie_thresholds_scale_with_ttl() {
let large_ttl = Duration::from_secs(120);
assert!(!super::is_zombie(
Duration::from_secs(200),
false,
false,
false,
large_ttl
));
assert!(super::is_zombie(
Duration::from_secs(400),
false,
false,
false,
large_ttl
));
assert!(!super::is_zombie(
Duration::from_secs(400),
false,
true,
false,
large_ttl
));
assert!(super::is_zombie(
Duration::from_secs(721),
false,
true,
false,
large_ttl
));
}
#[tokio::test]
async fn peer_send_timeout_drops_when_channel_saturated() {
const SEND_TIMEOUT: Duration = Duration::from_millis(500);
let (tx, _rx) = mpsc::channel::<u32>(1);
tx.try_send(0).expect("first send must succeed");
let start = Instant::now();
let result = timeout(SEND_TIMEOUT, tx.reserve()).await;
let elapsed = start.elapsed();
assert!(
result.is_err(),
"reserve must time out when channel is saturated"
);
assert!(
elapsed >= SEND_TIMEOUT,
"must wait the full {SEND_TIMEOUT:?} before giving up, waited {elapsed:?}"
);
assert!(
elapsed < SEND_TIMEOUT + Duration::from_millis(500),
"must not block significantly past {SEND_TIMEOUT:?}, took {elapsed:?}"
);
}
#[tokio::test]
async fn peer_send_succeeds_when_consumer_drains_in_time() {
const SEND_TIMEOUT: Duration = Duration::from_millis(500);
let (tx, mut rx) = mpsc::channel::<u32>(1);
tx.try_send(0).expect("first send must succeed");
let consumer = GlobalExecutor::spawn(async move {
sleep(Duration::from_millis(50)).await;
rx.recv().await.expect("must drain pre-filled entry");
rx.recv().await.expect("must receive reserved value")
});
let result = timeout(SEND_TIMEOUT, tx.reserve()).await;
let permit = result
.expect("reserve must complete within timeout")
.expect("channel must be open");
permit.send(42u32);
let received = consumer.await.expect("consumer task must succeed");
assert_eq!(received, 42, "reserved permit must deliver our value");
}
#[tokio::test]
async fn stream_send_pattern_fires_completion_tx_on_timeout() {
use crate::transport::BroadcastDeliveryOutcome;
const SEND_TIMEOUT: Duration = Duration::from_millis(100);
let (tx, _rx) = mpsc::channel::<u32>(1);
tx.try_send(0).expect("first send must succeed");
let (completion_tx, completion_rx) =
tokio::sync::oneshot::channel::<BroadcastDeliveryOutcome>();
match tokio::time::timeout(SEND_TIMEOUT, tx.reserve()).await {
Ok(Ok(_permit)) => {
panic!("reserve unexpectedly succeeded on saturated channel");
}
Ok(Err(_closed)) => {
let _ignored = completion_tx.send(BroadcastDeliveryOutcome::Dropped);
}
Err(_timeout) => {
let _ignored = completion_tx.send(BroadcastDeliveryOutcome::Dropped);
}
}
let signal = timeout(Duration::from_millis(50), completion_rx).await;
assert!(
matches!(signal, Ok(Ok(BroadcastDeliveryOutcome::Dropped))),
"completion_tx must be fired on the dispatch timeout path so the \
broadcast queue releases its permit immediately (not after \
120s STREAM_COMPLETION_TIMEOUT). Got {signal:?}"
);
}
#[test]
fn stream_send_branch_uses_timed_reserve_pattern() {
let src = include_str!("p2p_protoc.rs");
let needle = "ConnEvent::StreamSend {";
let mut occurrences = src.match_indices(needle);
let (idx, _) = occurrences
.next()
.expect("StreamSend branch must exist in p2p_protoc.rs");
let pipe_stream_needle = "ConnEvent::PipeStream {";
let pipe_stream_rel = src[idx..]
.find(pipe_stream_needle)
.expect("PipeStream branch must follow StreamSend in p2p_protoc.rs");
let window_end = idx + pipe_stream_rel;
let body = &src[idx..window_end];
assert!(
body.contains("tokio::time::timeout("),
"StreamSend dispatch must wrap the peer send in a timeout — \
see #4145 (gateway wedge). Body:\n{body}"
);
assert!(
body.contains(".reserve()"),
"StreamSend dispatch must use `reserve()` (not `send().await`) \
so we keep ownership of completion_tx on the timeout path. \
See #4145. Body:\n{body}"
);
assert!(
body.contains("SEND_TIMEOUT"),
"StreamSend dispatch must define a named SEND_TIMEOUT constant \
so the bound is greppable. See #4145. Body:\n{body}"
);
let fire_count = body
.matches("tx.send(BroadcastDeliveryOutcome::Dropped)")
.count();
assert_eq!(
fire_count, 3,
"StreamSend dispatch must signal completion_tx with \
BroadcastDeliveryOutcome::Dropped in all three non-success arms \
(closed / timeout / no-connection) — found {fire_count} such \
occurrences, expected exactly 3. If you REMOVED one, a refactor \
reintroduced the 120 s STREAM_COMPLETION_TIMEOUT stall (#4145). \
If you relabeled a drop arm as Delivered, you reintroduced the \
delivery/permit conflation (#4235). If you ADDED a new arm with \
its own fire, bump this count to match. Body:\n{body}"
);
}
#[test]
fn pipe_stream_branch_uses_timed_reserve_pattern() {
let src = include_str!("p2p_protoc.rs");
let needle = "ConnEvent::PipeStream {";
let mut occurrences = src.match_indices(needle);
let (idx, _) = occurrences
.next()
.expect("PipeStream branch must exist in p2p_protoc.rs");
let closed_channel_rel = src[idx..]
.find("ConnEvent::ClosedChannel(")
.expect("ClosedChannel branch must follow PipeStream in p2p_protoc.rs");
let window_end = idx + closed_channel_rel;
let body = &src[idx..window_end];
assert!(
body.contains("tokio::time::timeout("),
"PipeStream dispatch must wrap the peer send in a timeout — see #4145. Body:\n{body}"
);
assert!(
body.contains(".reserve()"),
"PipeStream dispatch must use `reserve()` instead of `send().await` — \
see #4145. Body:\n{body}"
);
}
#[test]
fn query_subscriptions_diagnostics_bounded_and_non_fatal() {
let src = include_str!("p2p_protoc.rs");
let needle = concat!("ContractHandlerEvent::", "QuerySubscriptions {");
let sites: Vec<usize> = src.match_indices(needle).map(|(i, _)| i).collect();
assert_eq!(
sites.len(),
1,
"the QuerySubscriptions handler query must live only in \
query_app_subscriptions_bounded, found {} sites",
sites.len()
);
let idx = sites[0];
let forward: String = src[idx..].chars().take(220).collect();
assert!(
!forward.contains(".await?"),
"the QuerySubscriptions handler query must not use the fatal `.await?` — a per-op \
handler timeout must never kill the network event listener (#4549). Near:\n{forward}"
);
let before = &src[..idx];
let bounded = before
.rfind("timeout(")
.is_some_and(|tp| src[tp..idx].contains("QUERY_SUBSCRIPTIONS_HANDLER_TIMEOUT"));
assert!(
bounded,
"the QuerySubscriptions handler query must be wrapped in \
timeout(QUERY_SUBSCRIPTIONS_HANDLER_TIMEOUT, ..) so a saturated contract handler \
can neither stall nor kill the network event loop (#4549)."
);
assert!(
src.matches("query_app_subscriptions_bounded(").count() >= 3,
"both diagnostics arms must delegate to query_app_subscriptions_bounded (#4549)"
);
assert!(
super::QUERY_SUBSCRIPTIONS_HANDLER_TIMEOUT <= Duration::from_secs(30),
"QUERY_SUBSCRIPTIONS_HANDLER_TIMEOUT must stay small to bound the loop stall (#4549)"
);
}
#[tokio::test(start_paused = true)]
async fn query_app_subscriptions_bounded_yields_empty_when_handler_stalls() {
let (sender, _handler_half, _waiting) = crate::contract::contract_handler_channel();
let bound = super::QUERY_SUBSCRIPTIONS_HANDLER_TIMEOUT;
let start = Instant::now();
let subs = super::query_app_subscriptions_bounded(&sender).await;
let elapsed = start.elapsed();
assert!(
subs.is_empty(),
"a stalled handler must yield empty app subscriptions, got {} entries",
subs.len()
);
assert!(
elapsed >= bound,
"must wait the bounded timeout ({elapsed:?} < {bound:?})"
);
assert!(
elapsed < Duration::from_secs(60),
"must not stall for the 300s handler timeout ({elapsed:?})"
);
}
#[test]
fn transaction_orphaned_handler_sends_cause_before_dropping_sender() {
const SOURCE: &str = include_str!("p2p_protoc.rs");
let arm_anchor = "NodeEvent::TransactionOrphaned { tx, peer } => {";
let arm_start = SOURCE
.find(arm_anchor)
.expect("TransactionOrphaned handler renamed or removed");
let arm_end = SOURCE[arm_start..]
.find("\n }\n")
.map(|p| arm_start + p)
.expect("TransactionOrphaned arm closer not found");
let body = &SOURCE[arm_start..arm_end];
let send_pos = body
.find("WaiterReply::PeerDisconnected")
.expect("missing PeerDisconnected delivery — #4313 cause is dropped");
let remove_pos = body
.find("pending_op_results.remove(&tx)")
.expect("missing pending_op_results.remove — sender never dropped");
assert!(
send_pos > remove_pos,
"the PeerDisconnected send must happen on the sender taken out by \
pending_op_results.remove (send-before-drop). Arm body:\n{body}"
);
}
#[test]
fn broadcast_state_to_peers_gates_summarize_on_hosted_or_in_use() {
const SOURCE: &str = include_str!("p2p_protoc/broadcast.rs");
let fn_anchor = "async fn broadcast_state_to_peers(";
let fn_start = SOURCE
.find(fn_anchor)
.expect("broadcast_state_to_peers renamed or removed");
let after_header = &SOURCE[fn_start + fn_anchor.len()..];
let body_end = [
after_header.find("\n async fn "),
after_header.find("\n pub(super) async fn "),
]
.into_iter()
.flatten()
.min()
.map(|p| fn_start + fn_anchor.len() + p)
.unwrap_or(SOURCE.len());
let body = &SOURCE[fn_start..body_end];
let gate_pos = body.find("should_broadcast_contract(op_manager").expect(
"broadcast_state_to_peers must gate on should_broadcast_contract — a \
bare get_contract_summary here reintroduces the #4473 summarize storm \
on the sim-inline broadcast path",
);
let summarize_pos = body
.find("get_contract_summary(")
.expect("broadcast_state_to_peers get_contract_summary call not found");
assert!(
gate_pos < summarize_pos,
"broadcast_state_to_peers must call should_broadcast_contract (offset \
{gate_pos}) BEFORE get_contract_summary (offset {summarize_pos}) so the \
fan-out is skipped for a contract we hold no state for. Body:\n{body}"
);
}
#[test]
fn handle_broadcast_state_change_gates_banned_egress() {
const SOURCE: &str = include_str!("p2p_protoc/broadcast.rs");
let fn_anchor = "async fn handle_broadcast_state_change(";
let fn_start = SOURCE
.find(fn_anchor)
.expect("handle_broadcast_state_change renamed or removed");
let after_header = &SOURCE[fn_start + fn_anchor.len()..];
let body_end = [
after_header.find("\n async fn "),
after_header.find("\n pub(super) async fn "),
]
.into_iter()
.flatten()
.min()
.map(|p| fn_start + fn_anchor.len() + p)
.unwrap_or(SOURCE.len());
let body = &SOURCE[fn_start..body_end];
let gate_pos = body.find("contract_ban_list.is_banned").expect(
"handle_broadcast_state_change is missing the ban-list egress gate — \
banned contracts would continue to be fanned out to subscribers \
(defeats the Phase 7 egress self-block, #4300)",
);
let target_pos = body
.find("get_broadcast_targets_update")
.expect("handle_broadcast_state_change is missing get_broadcast_targets_update");
assert!(
gate_pos < target_pos,
"BroadcastStateChange ban-list gate (offset {gate_pos}) must run \
BEFORE target resolution (offset {target_pos}) so a banned \
contract's broadcast is skipped — including the no-target retry \
re-emission. Body:\n{body}"
);
let banned_branch = &body[gate_pos..];
let return_pos = banned_branch
.find("return;")
.expect("banned-egress branch must early-return");
let banned_branch = &banned_branch[..return_pos];
assert!(
banned_branch.contains("self.broadcast_retries.remove(&key)"),
"banned-egress skip must clear broadcast_retries for the contract \
so a ban mid-retry-cycle doesn't leave a stale entry. Branch:\n{banned_branch}"
);
assert!(
banned_branch.contains("self.broadcast_no_target_streak.remove(&key)"),
"banned-egress skip must clear broadcast_no_target_streak for the \
contract so a ban mid-retry-cycle doesn't leave a stale entry. \
Branch:\n{banned_branch}"
);
}
#[test]
fn handle_broadcast_state_change_stashes_on_giveup_and_takes_on_targets() {
const SOURCE: &str = include_str!("p2p_protoc/broadcast.rs");
let fn_anchor = "async fn handle_broadcast_state_change(";
let fn_start = SOURCE
.find(fn_anchor)
.expect("handle_broadcast_state_change renamed or removed (#4359 wiring pin)");
let after_header = &SOURCE[fn_start + fn_anchor.len()..];
let body_end = [
after_header.find("\n async fn "),
after_header.find("\n pub(super) async fn "),
]
.into_iter()
.flatten()
.min()
.map(|p| fn_start + fn_anchor.len() + p)
.unwrap_or(SOURCE.len());
let body = &SOURCE[fn_start..body_end];
let empty_branch_anchor = "if target_result.targets.is_empty() {";
let empty_start = body.find(empty_branch_anchor).expect(
"#4359: targets-empty branch anchor missing — give-up stash wiring cannot be located",
);
let empty_branch = &body[empty_start..];
let empty_return = empty_branch
.find("\n return;")
.expect("#4359: targets-empty branch must early-return before the targets-found path");
let giveup_branch = &empty_branch[..empty_return];
let targets_found_branch = &empty_branch[empty_return..];
assert!(
giveup_branch.contains("pending_broadcasts\n .stash(")
|| giveup_branch.contains("pending_broadcasts.stash("),
"#4359: the broadcast give-up (retries-exhausted) branch MUST stash the state in \
op_manager.pending_broadcasts — otherwise a fresh-contract PUT that loses the \
broadcast/interest-resolve race is permanently abandoned (locally-hosted only). \
Give-up branch:\n{giveup_branch}"
);
assert!(
giveup_branch.contains("pending_broadcast_stash_recheck"),
"#4359: the give-up branch MUST re-resolve targets after stashing (the \
pending_broadcast_stash_recheck guard) to close the stash-after-flush \
lost-wakeup race. Give-up branch:\n{giveup_branch}"
);
assert!(
targets_found_branch.contains("pending_broadcasts.take("),
"#4359: the targets-found branch MUST take()/clear any stashed deferred broadcast \
so a recovered contract is not later re-emitted with superseded state. \
Targets-found branch:\n{targets_found_branch}"
);
}
fn strip_cfg_test_regions(src: &str) -> String {
const MARKER: &str = "#[cfg(test)]";
let bytes = src.as_bytes();
fn skip_lexical(bytes: &[u8], pos: usize) -> Option<usize> {
let at = |o: usize| bytes.get(o).copied();
if at(pos) == Some(b'/') && at(pos + 1) == Some(b'/') {
let mut j = pos + 2;
while j < bytes.len() && bytes[j] != b'\n' {
j += 1;
}
return Some((j + 1).min(bytes.len()));
}
if at(pos) == Some(b'/') && at(pos + 1) == Some(b'*') {
let mut depth = 0usize;
let mut j = pos;
while j < bytes.len() {
if bytes[j] == b'/' && at(j + 1) == Some(b'*') {
depth += 1;
j += 2;
} else if bytes[j] == b'*' && at(j + 1) == Some(b'/') {
depth -= 1;
j += 2;
if depth == 0 {
return Some(j);
}
} else {
j += 1;
}
}
return Some(bytes.len());
}
if at(pos) == Some(b'"') {
let mut j = pos + 1;
while j < bytes.len() {
match bytes[j] {
b'\\' => j += 2, b'"' => return Some(j + 1),
_ => j += 1,
}
}
return Some(bytes.len());
}
None
}
let mut out = String::with_capacity(src.len());
let mut i = 0usize;
while i < src.len() {
if src[i..].starts_with(MARKER) {
let mut j = i + MARKER.len();
loop {
while j < src.len() && bytes[j].is_ascii_whitespace() {
j += 1;
}
if bytes.get(j) == Some(&b'#') && bytes.get(j + 1) == Some(&b'[') {
let mut depth = 0i32;
while j < src.len() {
if let Some(nj) = skip_lexical(bytes, j) {
j = nj;
continue;
}
match bytes[j] {
b'[' => depth += 1,
b']' => {
depth -= 1;
if depth == 0 {
j += 1;
break;
}
}
_ => {}
}
j += 1;
}
continue;
}
break;
}
let mut k = j;
let mut found_brace = false;
while k < src.len() {
if let Some(nk) = skip_lexical(bytes, k) {
k = nk;
continue;
}
match bytes[k] {
b'{' => {
found_brace = true;
break;
}
b';' => break,
_ => {}
}
k += 1;
}
if found_brace {
let mut depth = 0i32;
while k < src.len() {
if let Some(nk) = skip_lexical(bytes, k) {
k = nk;
continue;
}
match bytes[k] {
b'{' => depth += 1,
b'}' => {
depth -= 1;
if depth == 0 {
k += 1;
break;
}
}
_ => {}
}
k += 1;
}
} else if k < src.len() {
k += 1;
}
i = k;
continue;
}
let ch = src[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
out
}
fn collect_rs_files(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_rs_files(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
out.push(path);
}
}
}
#[test]
fn pending_broadcast_flush_wired_at_all_interest_sites() {
let known_wired: std::collections::BTreeSet<&str> = [
"node.rs",
"operations/subscribe.rs",
"operations/get/op_ctx_task.rs",
]
.into_iter()
.collect();
let src_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files = Vec::new();
collect_rs_files(&src_root, &mut files);
assert!(
!files.is_empty(),
"#4359 MUST-FIX 1: source walk found no .rs files under {} — the pin cannot \
guarantee completeness if it can't read the crate source.",
src_root.display()
);
let mut found_with_register: std::collections::BTreeSet<String> = Default::default();
let mut pairing_failures: Vec<(String, usize, usize)> = Vec::new();
for path in &files {
let rel = path
.strip_prefix(&src_root)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
let src = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(_) => continue,
};
let prod = strip_cfg_test_regions(&src);
let reg_count = prod.matches("register_peer_interest(").count();
if reg_count == 0 {
continue;
}
if prod.contains("fn register_peer_interest(") {
continue;
}
found_with_register.insert(rel.clone());
let flush_count = prod.matches("flush_pending_broadcast_on_interest(").count();
if flush_count < reg_count {
pairing_failures.push((rel, reg_count, flush_count));
}
}
let found_refs: std::collections::BTreeSet<&str> =
found_with_register.iter().map(|s| s.as_str()).collect();
assert_eq!(
found_refs, known_wired,
"#4359 MUST-FIX 1: the set of production files that call \
register_peer_interest changed. Found {found_refs:?}, expected {known_wired:?}. \
A NEW register_peer_interest call site can be the first viable broadcast target \
for a cold id; it MUST flush the deferred broadcast (call \
flush_pending_broadcast_on_interest) or the #4359 fix silently fails for that \
path. Wire the flush, then add the file to `known_wired` in this pin. If the new \
site genuinely cannot be a first viable target, document why and adjust this pin."
);
assert!(
pairing_failures.is_empty(),
"#4359 MUST-FIX 1: interest registration without a paired broadcast flush: \
{pairing_failures:?} (file, register_peer_interest count, \
flush_pending_broadcast_on_interest count). Every interest registration that can \
be the first viable broadcast target for a cold id must flush the deferred \
broadcast, or the fix silently fails for that path."
);
let node_path = src_root.join("node.rs");
let node_src = std::fs::read_to_string(&node_path).expect("node.rs must be readable");
let node_prod = strip_cfg_test_regions(&node_src);
assert!(
node_prod.contains("Source 1 / proximity")
&& node_prod.contains("flush_pending_broadcast_on_interest(&key)"),
"#4359 MUST-FIX 1: the NeighborHosting proximity overlap path in node.rs must flush \
the deferred broadcast (Source 1 first-viable-target signal), or a cold-id PUT whose \
first target arrives via proximity announcement never drains."
);
}
#[tokio::test]
async fn full_bridge_channel_try_send_does_not_block() {
let (tx, _rx) = mpsc::channel::<u32>(1);
tx.try_send(1).expect("first send fills the single slot");
let res = timeout(Duration::from_millis(500), async { tx.try_send(2) })
.await
.expect("try_send must return immediately, never block the event loop");
match res {
Err(mpsc::error::TrySendError::Full(v)) => assert_eq!(v, 2),
other => panic!("expected TrySendError::Full, got {other:?}"),
}
}
#[tokio::test]
async fn dropped_connect_peer_does_not_hang_resend_waiter() {
let (callback, mut result) = mpsc::channel::<u32>(10);
drop(callback);
let recv = timeout(Duration::from_secs(1), result.recv())
.await
.expect("result.recv() must resolve immediately when callback dropped, not hang");
assert!(
recv.is_none(),
"dropping the callback must close the result channel (recv -> None), \
so the resend-waiter short-circuits instead of waiting 20s"
);
}
fn production_src() -> String {
let full = include_str!("p2p_protoc.rs");
let cut = full
.find("\nmod tests {")
.expect("p2p_protoc.rs must have a `mod tests {` block");
[
&full[..cut],
include_str!("p2p_protoc/migration.rs"),
include_str!("p2p_protoc/broadcast.rs"),
include_str!("p2p_protoc/dispatch.rs"),
include_str!("p2p_protoc/connection_lifecycle.rs"),
]
.join("\n")
}
#[test]
fn placement_migration_disabled_by_flag() {
assert!(
!super::placement_migration_enabled(None),
"production nodes (no subscribe_hint_floor_override) must have placement \
migration OFF (PLACEMENT_MIGRATION_ENABLED == false, 0.2.88 \
migration-storm hotfix). Flipping it on re-enables the SubscribeHint \
placement-migration storm; that is a placement-migration-surface change \
requiring Ian (@sanity) sign-off and the #4440 staged re-enable plan."
);
assert!(
super::placement_migration_enabled(Some(super::SUBSCRIBE_HINT_MIN_VERSION)),
"a simulation node (subscribe_hint_floor_override present) must NOT be \
blanket-blocked by the production kill switch — migration on/off is \
decided by the floor override so test coverage of the mechanism is \
preserved"
);
assert!(super::placement_migration_enabled(Some((0, 0, 0))));
}
#[test]
fn placement_migration_emit_sites_gated_by_flag() {
let src = production_src();
let guard = "if !placement_migration_enabled(";
for (func, work_anchor) in [
(
"fn consider_contract_migration(",
"select_migration_target(",
),
(
"fn consider_migration_for_new_peer(",
"hosting_contract_keys(",
),
] {
let start = src
.find(func)
.unwrap_or_else(|| panic!("emit site `{func}` not found in production source"));
let body = &src[start..];
let guard_at = body.find(guard).unwrap_or_else(|| {
panic!(
"emit site `{func}` MUST early-return on `{guard}` (0.2.88 kill \
switch) before emitting any SubscribeHint nudge"
)
});
let work_at = body.find(work_anchor).unwrap_or_else(|| {
panic!("expected `{work_anchor}` in `{func}` body (anchor moved?)")
});
assert!(
guard_at < work_at,
"emit site `{func}`: the `{guard}` kill-switch guard must come BEFORE \
`{work_anchor}` so no nudge work runs while migration is disabled"
);
}
}
#[test]
fn placement_migration_inbound_act_gated_by_flag() {
let node_src = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/node.rs"),
)
.expect("read crates/core/src/node.rs");
let handler_at = node_src
.find("NetMessageV1::SubscribeHint(hint) =>")
.expect("SubscribeHint inbound handler arm not found in node.rs");
let body = &node_src[handler_at..];
let guard_at = body.find("placement_migration_enabled(").expect(
"inbound SubscribeHint handler MUST gate on placement_migration_enabled(..) \
(0.2.88 kill switch) before acting on a hint",
);
let act_at = body.find("start_directed_subscribe(").expect(
"expected start_directed_subscribe( in the SubscribeHint handler (anchor moved?)",
);
assert!(
guard_at < act_at,
"the placement_migration_enabled() kill-switch guard must come BEFORE \
start_directed_subscribe() so a disabled node never acts on an inbound hint"
);
}
#[test]
fn connect_peer_enqueue_is_non_blocking() {
let src = production_src();
let try_send_at = src.find("ev_listener_tx.try_send(").expect(
"#4145 SITE 1: the ConnectPeer enqueue must use ev_listener_tx.try_send(), \
not a blocking .send().await (self-deadlock on the channel the loop drains).",
);
let waiter_at = src[try_send_at..]
.find("Duration::from_secs(20)")
.map(|rel| try_send_at + rel)
.expect(
"#4145 SITE 1: the resend-waiter (timeout 20s) must appear AFTER the \
ev_listener_tx.try_send — found it before, which risks a 20s hang on a \
dropped ConnectPeer.",
);
let between = &src[try_send_at..waiter_at];
assert!(
between.contains("continue"),
"#4145 SITE 1: the Full-path `continue` must short-circuit BEFORE the \
resend-waiter spawn, so a dropped ConnectPeer never spawns a waiter."
);
}
#[test]
fn inline_bridge_sends_are_non_blocking() {
let src = production_src();
let converted = src.matches("self.bridge.try_send(").count();
assert!(
converted >= 4,
"#4145 item 2: expected at least four inline-on-loop self.bridge.try_send(...) \
call sites (2 fan-out loops + 2 unicast handlers), found {converted}."
);
assert!(
!src.contains("self.bridge.send("),
"#4145 item 2: a blocking self.bridge.send(...).await remains on an \
inline-on-event-loop path — must be self.bridge.try_send()."
);
}
#[test]
fn handshake_commands_are_non_blocking() {
let src = production_src();
assert!(
!src.contains(".send(HandshakeCommand::"),
"#4145 SITES 2-7: a blocking .send(HandshakeCommand::...).await remains — \
every handshake command from the event loop must use try_send()."
);
let same_line = src.matches("try_send(HandshakeCommand::").count();
let multi_line = src.matches("try_send(").count().saturating_sub(same_line);
assert!(
same_line >= 6,
"#4145 SITES 2-7: expected at least six same-line \
try_send(HandshakeCommand::...) call sites, found {same_line}."
);
assert!(
multi_line >= 1,
"#4145: expected at least one multi-line try_send( call site."
);
}
#[tokio::test]
async fn connect_command_drop_fails_callbacks_without_backoff() {
use crate::dev_tool::Transaction;
use crate::transport::ExpectedInboundTracker;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
let mut state = super::EventListenerState::new(ExpectedInboundTracker::empty_for_test());
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 45678);
let tx = Transaction::new::<crate::operations::connect::ConnectMsg>();
let (cb1, mut rx1) = mpsc::channel::<Result<(SocketAddr, Option<usize>), ()>>(1);
let (cb2, mut rx2) = mpsc::channel::<Result<(SocketAddr, Option<usize>), ()>>(1);
state.awaiting_connection.insert(
peer_addr,
vec![
Box::new(cb1) as Box<dyn super::ConnectResultSender>,
Box::new(cb2) as Box<dyn super::ConnectResultSender>,
],
);
state.awaiting_connection_txs.insert(peer_addr, vec![tx]);
assert!(
!state.peer_backoff.is_in_backoff(peer_addr),
"precondition: peer must not be in backoff before the drop"
);
super::fail_awaiting_connection(&mut state, peer_addr, tx, false).await;
let r1 = timeout(Duration::from_secs(1), rx1.recv())
.await
.expect("callback 1 must resolve immediately, not park");
let r2 = timeout(Duration::from_secs(1), rx2.recv())
.await
.expect("callback 2 must resolve immediately, not park");
assert_eq!(r1, Some(Err(())), "callback 1 must receive Err(())");
assert_eq!(r2, Some(Err(())), "callback 2 must receive Err(())");
assert!(
!state.awaiting_connection.contains_key(&peer_addr),
"awaiting_connection must be drained for the peer"
);
assert!(
!state.awaiting_connection_txs.contains_key(&peer_addr),
"awaiting_connection_txs must be drained for the peer (lock-step with callbacks)"
);
assert!(
!state.peer_backoff.is_in_backoff(peer_addr),
"#4145 SITE 6: a LOCAL handshake-channel-full drop must NOT record peer \
backoff — that would wrongly delay a legitimate retry."
);
}
#[test]
fn connect_peer_full_handshake_channel_prunes_in_transit() {
let src = production_src();
let from = src
.find("try_send(HandshakeCommand::Connect {")
.expect("connect_peer must enqueue HandshakeCommand::Connect via try_send");
let end = (from + 1200).min(src.len());
let branch = &src[from..end];
assert!(
branch.contains("prune_in_transit_connection(peer_addr)"),
"#4145 SITE 6: the connect-command-drop branch must prune the in-transit \
connection slot so the dropped attempt does not leak a reservation."
);
assert!(
branch.contains("fail_awaiting_connection("),
"#4145 SITE 6: the connect-command-drop branch must fail awaiting callbacks \
via fail_awaiting_connection()."
);
assert!(
!branch.contains("record_failure"),
"#4145 SITE 6: the connect-command-drop branch must NOT record peer backoff \
(local channel contention is not a remote connection failure)."
);
}
}