use crate::common::ProtocolError;
use crate::core::hub::HubEvent;
use crate::pb::{
p2p_client::P2pClient as ProtoP2pClient, p2p_server::P2p as ProtoP2p, p2p_server::P2pServer as ProtoP2pServer, KaspadMessage,
};
use crate::{ConnectionInitializer, Router};
use futures::FutureExt;
use kaspa_core::{debug, info};
use kaspa_utils::networking::NetAddress;
use kaspa_utils_tower::{
counters::TowerConnectionCounters,
middleware::{measure_request_body_size_layer, CountBytesBody, MapResponseBodyLayer, ServiceBuilder},
};
use std::net::ToSocketAddrs;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use tokio::sync::mpsc::{channel as mpsc_channel, Sender as MpscSender};
use tokio::sync::oneshot::{channel as oneshot_channel, Sender as OneshotSender};
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;
use tonic::codegen::Body;
use tonic::transport::{Error as TonicError, Server as TonicServer};
use tonic::{Request, Response, Status as TonicStatus, Streaming};
#[derive(Error, Debug)]
pub enum ConnectionError {
#[error("missing socket address")]
NoAddress,
#[error("{0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
TonicError(#[from] TonicError),
#[error("{0}")]
TonicStatus(#[from] TonicStatus),
#[error("{0}")]
ProtocolError(#[from] ProtocolError),
}
const P2P_MAX_MESSAGE_SIZE: usize = 1024 * 1024 * 1024;
#[derive(Clone)]
pub struct ConnectionHandler {
hub_sender: MpscSender<HubEvent>,
initializer: Arc<dyn ConnectionInitializer>,
counters: Arc<TowerConnectionCounters>,
}
impl ConnectionHandler {
pub(crate) fn new(
hub_sender: MpscSender<HubEvent>,
initializer: Arc<dyn ConnectionInitializer>,
counters: Arc<TowerConnectionCounters>,
) -> Self {
Self { hub_sender, initializer, counters }
}
pub(crate) fn serve(&self, serve_address: NetAddress) -> Result<OneshotSender<()>, ConnectionError> {
let (termination_sender, termination_receiver) = oneshot_channel::<()>();
let connection_handler = self.clone();
info!("P2P Server starting on: {}", serve_address);
let bytes_tx = self.counters.bytes_tx.clone();
let bytes_rx = self.counters.bytes_rx.clone();
tokio::spawn(async move {
let proto_server = ProtoP2pServer::new(connection_handler)
.accept_compressed(tonic::codec::CompressionEncoding::Gzip)
.send_compressed(tonic::codec::CompressionEncoding::Gzip)
.max_decoding_message_size(P2P_MAX_MESSAGE_SIZE);
let serve_result = TonicServer::builder()
.layer(measure_request_body_size_layer(bytes_rx, |b| b))
.layer(MapResponseBodyLayer::new(move |body| CountBytesBody::new(body, bytes_tx.clone())))
.add_service(proto_server)
.serve_with_shutdown(serve_address.into(), termination_receiver.map(drop))
.await;
match serve_result {
Ok(_) => info!("P2P Server stopped: {}", serve_address),
Err(err) => panic!("P2P, Server {serve_address} stopped with error: {err:?}"),
}
});
Ok(termination_sender)
}
pub(crate) async fn connect(&self, peer_address: String) -> Result<Arc<Router>, ConnectionError> {
let Some(socket_address) = peer_address.to_socket_addrs()?.next() else {
return Err(ConnectionError::NoAddress);
};
let peer_address = format!("http://{}", peer_address);
let channel = tonic::transport::Endpoint::new(peer_address)?
.timeout(Duration::from_millis(Self::communication_timeout()))
.connect_timeout(Duration::from_millis(Self::connect_timeout()))
.tcp_keepalive(Some(Duration::from_millis(Self::keep_alive())))
.connect()
.await?;
let channel = ServiceBuilder::new()
.layer(MapResponseBodyLayer::new(move |body| CountBytesBody::new(body, self.counters.bytes_rx.clone())))
.layer(measure_request_body_size_layer(self.counters.bytes_tx.clone(), |body| {
body.map_err(|e| tonic::Status::from_error(Box::new(e))).boxed_unsync()
}))
.service(channel);
let mut client = ProtoP2pClient::new(channel)
.send_compressed(tonic::codec::CompressionEncoding::Gzip)
.accept_compressed(tonic::codec::CompressionEncoding::Gzip)
.max_decoding_message_size(P2P_MAX_MESSAGE_SIZE);
let (outgoing_route, outgoing_receiver) = mpsc_channel(Self::outgoing_network_channel_size());
let incoming_stream = client.message_stream(ReceiverStream::new(outgoing_receiver)).await?.into_inner();
let router = Router::new(socket_address, true, self.hub_sender.clone(), incoming_stream, outgoing_route).await;
match self.initializer.initialize_connection(router.clone()).await {
Ok(()) => {
self.hub_sender.send(HubEvent::NewPeer(router.clone())).await.expect("hub receiver should never drop before senders");
}
Err(err) => {
router.try_sending_reject_message(&err).await;
router.close().await;
debug!("P2P, handshake failed for outbound peer {}: {}", router, err);
return Err(ConnectionError::ProtocolError(err));
}
}
Ok(router)
}
pub(crate) async fn connect_with_retry(
&self,
address: String,
retry_attempts: u8,
retry_interval: Duration,
) -> Result<Arc<Router>, ConnectionError> {
let mut counter = 0;
loop {
counter += 1;
match self.connect(address.clone()).await {
Ok(router) => {
debug!("P2P, Client connected, peer: {:?}", address);
return Ok(router);
}
Err(ConnectionError::ProtocolError(err)) => {
debug!("P2P, connect retry #{} failed with error {:?}, peer: {:?}, aborting retries", counter, err, address);
return Err(ConnectionError::ProtocolError(err));
}
Err(err) => {
debug!("P2P, connect retry #{} failed with error {:?}, peer: {:?}", counter, err, address);
if counter < retry_attempts {
tokio::time::sleep(retry_interval).await;
} else {
debug!("P2P, Client connection retry #{} - all failed", retry_attempts);
return Err(err);
}
}
}
}
}
fn outgoing_network_channel_size() -> usize {
(1 << 17) + 256
}
fn communication_timeout() -> u64 {
10_000
}
fn keep_alive() -> u64 {
10_000
}
fn connect_timeout() -> u64 {
1_000
}
}
#[tonic::async_trait]
impl ProtoP2p for ConnectionHandler {
type MessageStreamStream = Pin<Box<dyn futures::Stream<Item = Result<KaspadMessage, TonicStatus>> + Send + 'static>>;
async fn message_stream(
&self,
request: Request<Streaming<KaspadMessage>>,
) -> Result<Response<Self::MessageStreamStream>, TonicStatus> {
let Some(remote_address) = request.remote_addr() else {
return Err(TonicStatus::new(tonic::Code::InvalidArgument, "Incoming connection opening request has no remote address"));
};
let (outgoing_route, outgoing_receiver) = mpsc_channel(Self::outgoing_network_channel_size());
let incoming_stream = request.into_inner();
let router = Router::new(remote_address, false, self.hub_sender.clone(), incoming_stream, outgoing_route).await;
self.hub_sender.send(HubEvent::NewPeer(router)).await.expect("hub receiver should never drop before senders");
Ok(Response::new(Box::pin(ReceiverStream::new(outgoing_receiver).map(Ok)) as Self::MessageStreamStream))
}
}