use crate::core::hub::HubEvent;
use crate::pb::KaspadMessage;
use crate::{common::ProtocolError, KaspadMessagePayloadType};
use kaspa_core::{debug, error, info, trace, warn};
use parking_lot::{Mutex, RwLock};
use std::fmt::Display;
use std::net::SocketAddr;
use std::{collections::HashMap, sync::Arc};
use tokio::select;
use tokio::sync::mpsc::{channel as mpsc_channel, Receiver as MpscReceiver, Sender as MpscSender};
use tokio::sync::oneshot::{channel as oneshot_channel, Sender as OneshotSender};
use tonic::Streaming;
use uuid::Uuid;
pub type IncomingRoute = MpscReceiver<KaspadMessage>;
#[derive(Debug)]
struct RouterMutableState {
start_signal: Option<OneshotSender<()>>,
shutdown_signal: Option<OneshotSender<()>>,
}
#[derive(Debug)]
pub struct Router {
identity: Uuid,
net_address: SocketAddr,
is_outbound: bool,
routing_map: RwLock<HashMap<KaspadMessagePayloadType, MpscSender<KaspadMessage>>>,
outgoing_route: MpscSender<KaspadMessage>,
hub_sender: MpscSender<HubEvent>,
mutable_state: Mutex<RouterMutableState>,
}
impl Display for Router {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.net_address)
}
}
impl Router {
pub(crate) async fn new(
net_address: SocketAddr,
is_outbound: bool,
hub_sender: MpscSender<HubEvent>,
mut incoming_stream: Streaming<KaspadMessage>,
outgoing_route: MpscSender<KaspadMessage>,
) -> Arc<Self> {
let (start_sender, start_receiver) = oneshot_channel();
let (shutdown_sender, mut shutdown_receiver) = oneshot_channel();
let router = Arc::new(Router {
identity: Uuid::new_v4(),
net_address,
is_outbound,
routing_map: RwLock::new(HashMap::new()),
outgoing_route,
hub_sender,
mutable_state: Mutex::new(RouterMutableState { start_signal: Some(start_sender), shutdown_signal: Some(shutdown_sender) }),
});
let router_clone = router.clone();
tokio::spawn(async move {
let _ = start_receiver.await;
loop {
select! {
biased;
_ = &mut shutdown_receiver => {
debug!("P2P, Router receive loop - shutdown signal received, exiting router receive loop, router-id: {}", router.identity);
break;
}
res = incoming_stream.message() => match res {
Ok(Some(msg)) => {
trace!("P2P, Router receive loop - got message: {:?}, router-id: {}", msg, router.identity);
let msg_str = msg_type_string(&msg);
if !(router.route_to_flow(msg).await) {
warn!("P2P, Router receive loop - no route for message {} - exiting loop, router-id: {}", msg_str, router.identity);
break;
}
}
Ok(None) => {
info!("P2P, Router receive loop - incoming stream ended for peer {}", router);
break;
}
Err(err) => {
warn!("P2P, Router receive loop - network error: {:?} from peer {}", err, router);
break;
}
}
}
}
router.close().await;
debug!("P2P, Router receive loop - exited, router-id: {}, router refs: {}", router.identity, Arc::strong_count(&router));
});
router_clone
.hub_sender
.send(HubEvent::NewPeer(router_clone.clone()))
.await
.expect("hub receiver should never drop before senders");
router_clone
}
pub fn identity(&self) -> Uuid {
self.identity
}
pub fn net_address(&self) -> SocketAddr {
self.net_address
}
pub fn is_outbound(&self) -> bool {
self.is_outbound
}
fn incoming_flow_channel_size() -> usize {
128
}
pub fn start(&self) {
let op = self.mutable_state.lock().start_signal.take();
if let Some(signal) = op {
let _ = signal.send(());
} else {
debug!("P2P, Router start was called more than once, router-id: {}", self.identity)
}
}
pub fn subscribe(&self, msg_types: Vec<KaspadMessagePayloadType>) -> IncomingRoute {
self.subscribe_with_capacity(msg_types, Self::incoming_flow_channel_size())
}
pub fn subscribe_with_capacity(&self, msg_types: Vec<KaspadMessagePayloadType>, capacity: usize) -> IncomingRoute {
let (sender, receiver) = mpsc_channel(capacity);
let mut map = self.routing_map.write();
for msg_type in msg_types {
match map.insert(msg_type, sender.clone()) {
Some(_) => {
error!("P2P, Router::subscribe overrides an existing value: {:?}, router-id: {}", msg_type, self.identity);
panic!("P2P, Tried to subscribe to an existing route");
}
None => {
trace!("P2P, Router::subscribe - msg_type: {:?} route is registered, router-id:{:?}", msg_type, self.identity);
}
}
}
receiver
}
pub async fn route_to_flow(&self, msg: KaspadMessage) -> bool {
if msg.payload.is_none() {
debug!("P2P, Route to flow got empty payload, router-id: {}", self.identity);
return false;
}
let msg_type: KaspadMessagePayloadType = msg.payload.as_ref().expect("payload was just verified").into();
let op = self.routing_map.read().get(&msg_type).cloned();
if let Some(sender) = op {
sender.send(msg).await.is_ok()
} else {
false
}
}
pub async fn enqueue(&self, msg: KaspadMessage) -> Result<(), ProtocolError> {
assert!(msg.payload.is_some(), "Kaspad P2P message should always have a value");
match self.outgoing_route.send(msg).await {
Ok(_r) => Ok(()),
Err(_e) => Err(ProtocolError::ConnectionClosed),
}
}
pub async fn close(&self) -> bool {
{
let mut state = self.mutable_state.lock();
if let Some(signal) = state.start_signal.take() {
let _ = signal.send(());
}
if let Some(signal) = state.shutdown_signal.take() {
let _ = signal.send(());
} else {
trace!("P2P, Router close was called more than once, router-id: {}", self.identity);
return false;
}
}
self.routing_map.write().clear();
self.hub_sender.send(HubEvent::PeerClosing(self.identity)).await.expect("hub receiver should never drop before senders");
true
}
}
fn msg_type_string(msg: &KaspadMessage) -> String {
match msg.payload.as_ref() {
Some(payload) => {
let payload_type: KaspadMessagePayloadType = payload.into();
format!("{:?}", payload_type)
}
None => "<EMPTY_PAYLOAD>".to_owned(),
}
}