feat(network): implement background keep-alive loop (#427)

This commit is contained in:
Santiago Carmuega 2024-04-02 21:27:33 -03:00 committed by GitHub
parent 3b7ac7ee38
commit df3291bef5
5 changed files with 145 additions and 109 deletions

View file

@ -5,7 +5,7 @@ use pallas::{
miniprotocols::{blockfetch, chainsync, keepalive, Point, MAINNET_MAGIC}, miniprotocols::{blockfetch, chainsync, keepalive, Point, MAINNET_MAGIC},
}, },
}; };
use std::time::Duration;
use thiserror::Error; use thiserror::Error;
use tokio::time::Instant; use tokio::time::Instant;
@ -117,14 +117,6 @@ async fn do_chainsync(
} }
} }
async fn do_keepalive(mut keepalive_client: keepalive::Client) -> Result<(), Error> {
loop {
tokio::time::sleep(Duration::from_secs(20)).await;
keepalive_client.send_keepalive().await?;
tracing::info!("keepalive sent");
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tracing::subscriber::set_global_default( tracing::subscriber::set_global_default(
@ -145,25 +137,10 @@ async fn main() {
plexer, plexer,
chainsync, chainsync,
blockfetch, blockfetch,
keepalive,
.. ..
} = peer; } = peer;
let chainsync_handle = tokio::spawn(do_chainsync(chainsync, blockfetch)); do_chainsync(chainsync, blockfetch).await.unwrap();
let keepalive_handle = tokio::spawn(do_keepalive(keepalive));
// If any of these concurrent tasks exit or fail, the others are canceled.
let (chainsync_result, keepalive_result) =
tokio::try_join!(chainsync_handle, keepalive_handle)
.expect("error joining tokio threads");
if let Err(err) = chainsync_result {
tracing::error!("chainsync error: {:?}", err);
}
if let Err(err) = keepalive_result {
tracing::error!("keepalive error: {:?}", err);
}
plexer.abort().await; plexer.abort().await;

View file

@ -1,7 +1,8 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::Path; use std::path::Path;
use std::time::Duration;
use thiserror::Error; use thiserror::Error;
use tracing::error; use tracing::{debug, error, warn};
use tokio::net::{TcpListener, ToSocketAddrs}; use tokio::net::{TcpListener, ToSocketAddrs};
@ -30,53 +31,104 @@ pub enum Error {
#[error("handshake protocol error")] #[error("handshake protocol error")]
HandshakeProtocol(handshake::Error), HandshakeProtocol(handshake::Error),
#[error("keepalive client loop error")]
KeepAliveClientLoop(keepalive::ClientError),
#[error("keepalive server loop error")]
KeepAliveServerLoop(keepalive::ServerError),
#[error("handshake version not accepted")] #[error("handshake version not accepted")]
IncompatibleVersion, IncompatibleVersion,
} }
pub const DEFAULT_KEEP_ALIVE_INTERVAL_SEC: u64 = 20;
pub type KeepAliveHandle = tokio::task::JoinHandle<Result<(), Error>>;
pub enum KeepAliveLoop {
Client(keepalive::Client, Duration),
Server(keepalive::Server),
}
impl KeepAliveLoop {
pub fn client(client: keepalive::Client, interval: Duration) -> Self {
Self::Client(client, interval)
}
pub fn server(server: keepalive::Server) -> Self {
Self::Server(server)
}
pub async fn run_client(
mut client: keepalive::Client,
interval: Duration,
) -> Result<(), Error> {
let mut interval = tokio::time::interval(interval);
loop {
interval.tick().await;
warn!("sending keepalive request");
client
.keepalive_roundtrip()
.await
.map_err(Error::KeepAliveClientLoop)?;
}
}
pub async fn run_server(mut server: keepalive::Server) -> Result<(), Error> {
loop {
debug!("waiting keepalive request");
server
.keepalive_roundtrip()
.await
.map_err(Error::KeepAliveServerLoop)?;
}
}
pub fn spawn(self) -> KeepAliveHandle {
match self {
KeepAliveLoop::Client(client, interval) => {
tokio::spawn(Self::run_client(client, interval))
}
KeepAliveLoop::Server(server) => tokio::spawn(Self::run_server(server)),
}
}
}
/// Client of N2N Ouroboros /// Client of N2N Ouroboros
pub struct PeerClient { pub struct PeerClient {
pub plexer: RunningPlexer, pub plexer: RunningPlexer,
pub handshake: handshake::N2NClient, pub keepalive: KeepAliveHandle,
pub chainsync: chainsync::N2NClient, pub chainsync: chainsync::N2NClient,
pub blockfetch: blockfetch::Client, pub blockfetch: blockfetch::Client,
pub txsubmission: txsubmission::Client, pub txsubmission: txsubmission::Client,
pub keepalive: keepalive::Client,
} }
impl PeerClient { impl PeerClient {
pub fn new(bearer: Bearer) -> Self {
let mut plexer = multiplexer::Plexer::new(bearer);
let hs_channel = plexer.subscribe_client(PROTOCOL_N2N_HANDSHAKE);
let cs_channel = plexer.subscribe_client(PROTOCOL_N2N_CHAIN_SYNC);
let bf_channel = plexer.subscribe_client(PROTOCOL_N2N_BLOCK_FETCH);
let txsub_channel = plexer.subscribe_client(PROTOCOL_N2N_TX_SUBMISSION);
let keepalive_channel = plexer.subscribe_client(PROTOCOL_N2N_KEEP_ALIVE);
let plexer = plexer.spawn();
Self {
plexer,
handshake: handshake::Client::new(hs_channel),
chainsync: chainsync::Client::new(cs_channel),
blockfetch: blockfetch::Client::new(bf_channel),
txsubmission: txsubmission::Client::new(txsub_channel),
keepalive: keepalive::Client::new(keepalive_channel),
}
}
pub async fn connect(addr: impl ToSocketAddrs, magic: u64) -> Result<Self, Error> { pub async fn connect(addr: impl ToSocketAddrs, magic: u64) -> Result<Self, Error> {
let bearer = Bearer::connect_tcp(addr) let bearer = Bearer::connect_tcp(addr)
.await .await
.map_err(Error::ConnectFailure)?; .map_err(Error::ConnectFailure)?;
let mut client = Self::new(bearer); let mut plexer = multiplexer::Plexer::new(bearer);
let channel = plexer.subscribe_client(PROTOCOL_N2N_HANDSHAKE);
let mut handshake = handshake::Client::new(channel);
let cs_channel = plexer.subscribe_client(PROTOCOL_N2N_CHAIN_SYNC);
let bf_channel = plexer.subscribe_client(PROTOCOL_N2N_BLOCK_FETCH);
let txsub_channel = plexer.subscribe_client(PROTOCOL_N2N_TX_SUBMISSION);
let channel = plexer.subscribe_client(PROTOCOL_N2N_KEEP_ALIVE);
let keepalive = keepalive::Client::new(channel);
let plexer = plexer.spawn();
let versions = handshake::n2n::VersionTable::v7_and_above(magic); let versions = handshake::n2n::VersionTable::v7_and_above(magic);
let handshake = client let handshake = handshake
.handshake()
.handshake(versions) .handshake(versions)
.await .await
.map_err(Error::HandshakeProtocol)?; .map_err(Error::HandshakeProtocol)?;
@ -86,11 +138,21 @@ impl PeerClient {
return Err(Error::IncompatibleVersion); return Err(Error::IncompatibleVersion);
} }
Ok(client) let keepalive = KeepAliveLoop::client(
} keepalive,
Duration::from_secs(DEFAULT_KEEP_ALIVE_INTERVAL_SEC),
)
.spawn();
pub fn handshake(&mut self) -> &mut handshake::N2NClient { let client = Self {
&mut self.handshake plexer,
keepalive,
chainsync: chainsync::Client::new(cs_channel),
blockfetch: blockfetch::Client::new(bf_channel),
txsubmission: txsubmission::Client::new(txsub_channel),
};
Ok(client)
} }
pub fn chainsync(&mut self) -> &mut chainsync::N2NClient { pub fn chainsync(&mut self) -> &mut chainsync::N2NClient {
@ -114,10 +176,6 @@ impl PeerClient {
&mut self.txsubmission &mut self.txsubmission
} }
pub fn keepalive(&mut self) -> &mut keepalive::Client {
&mut self.keepalive
}
pub async fn abort(self) { pub async fn abort(self) {
self.plexer.abort().await self.plexer.abort().await
} }

View file

@ -27,19 +27,11 @@ pub enum ClientError {
Plexer(multiplexer::Error), Plexer(multiplexer::Error),
} }
pub struct KeepAliveSharedState { pub struct Client(State, multiplexer::ChannelBuffer);
saved_cookie: u16,
}
pub struct Client(State, multiplexer::ChannelBuffer, KeepAliveSharedState);
impl Client { impl Client {
pub fn new(channel: multiplexer::AgentChannel) -> Self { pub fn new(channel: multiplexer::AgentChannel) -> Self {
Self( Self(State::Client, multiplexer::ChannelBuffer::new(channel))
State::Client,
multiplexer::ChannelBuffer::new(channel),
KeepAliveSharedState { saved_cookie: 0 },
)
} }
pub fn state(&self) -> &State { pub fn state(&self) -> &State {
@ -53,7 +45,7 @@ impl Client {
fn has_agency(&self) -> bool { fn has_agency(&self) -> bool {
match &self.0 { match &self.0 {
State::Client => true, State::Client => true,
State::Server => false, State::Server(..) => false,
State::Done => false, State::Done => false,
} }
} }
@ -84,7 +76,7 @@ impl Client {
fn assert_inbound_state(&self, msg: &Message) -> Result<(), ClientError> { fn assert_inbound_state(&self, msg: &Message) -> Result<(), ClientError> {
match (&self.0, msg) { match (&self.0, msg) {
(State::Server, Message::ResponseKeepAlive(..)) => Ok(()), (State::Server(..), Message::ResponseKeepAlive(..)) => Ok(()),
_ => Err(ClientError::InvalidInbound), _ => Err(ClientError::InvalidInbound),
} }
} }
@ -108,32 +100,38 @@ impl Client {
Ok(msg) Ok(msg)
} }
pub async fn send_keepalive(&mut self) -> Result<(), ClientError> { pub async fn send_keepalive_request(&mut self) -> Result<(), ClientError> {
// generate random cookie value // generate random cookie value
let cookie = rand::thread_rng().gen::<KeepAliveCookie>(); let cookie = rand::thread_rng().gen::<Cookie>();
let msg = Message::KeepAlive(cookie); let msg = Message::KeepAlive(cookie);
self.send_message(&msg).await?; self.send_message(&msg).await?;
self.2.saved_cookie = cookie; self.0 = State::Server(cookie);
self.0 = State::Server;
debug!("sent keepalive message with cookie {}", cookie); debug!("sent keepalive message with cookie {}", cookie);
self.recv_while_sending_keepalive().await?;
Ok(()) Ok(())
} }
async fn recv_while_sending_keepalive(&mut self) -> Result<(), ClientError> { pub async fn recv_keepalive_response(&mut self) -> Result<(), ClientError> {
match self.recv_message().await? { match self.recv_message().await? {
Message::ResponseKeepAlive(cookie) => { Message::ResponseKeepAlive(cookie) => {
debug!("received keepalive response with cookie {}", cookie); debug!("received keepalive response with cookie {}", cookie);
if cookie == self.2.saved_cookie { match self.state() {
self.0 = State::Client; State::Server(expected) if *expected == cookie => {
Ok(()) self.0 = State::Client;
} else { Ok(())
Err(ClientError::KeepAliveCookieMismatch) }
State::Server(..) => Err(ClientError::KeepAliveCookieMismatch),
_ => unreachable!(),
} }
} }
_ => Err(ClientError::InvalidInbound), _ => Err(ClientError::InvalidInbound),
} }
} }
pub async fn keepalive_roundtrip(&mut self) -> Result<(), ClientError> {
self.send_keepalive_request().await?;
self.recv_keepalive_response().await?;
Ok(())
}
} }

View file

@ -1,15 +1,15 @@
pub type KeepAliveCookie = u16; pub type Cookie = u16;
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum State { pub enum State {
Client, Client,
Server, Server(Cookie),
Done, Done,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Message { pub enum Message {
KeepAlive(KeepAliveCookie), KeepAlive(Cookie),
ResponseKeepAlive(KeepAliveCookie), ResponseKeepAlive(Cookie),
Done, Done,
} }

View file

@ -41,7 +41,7 @@ impl Server {
fn has_agency(&self) -> bool { fn has_agency(&self) -> bool {
match &self.0 { match &self.0 {
State::Client => false, State::Client => false,
State::Server => true, State::Server(..) => true,
State::Done => false, State::Done => false,
} }
} }
@ -64,8 +64,7 @@ impl Server {
fn assert_outbound_state(&self, msg: &Message) -> Result<(), ServerError> { fn assert_outbound_state(&self, msg: &Message) -> Result<(), ServerError> {
match (&self.0, msg) { match (&self.0, msg) {
(State::Server, Message::ResponseKeepAlive(..)) => Ok(()), (State::Server(..), Message::ResponseKeepAlive(..)) => Ok(()),
_ => Err(ServerError::InvalidOutbound), _ => Err(ServerError::InvalidOutbound),
} }
} }
@ -97,33 +96,37 @@ impl Server {
Ok(msg) Ok(msg)
} }
pub async fn send_keepalive_response( pub async fn recv_keepalive_request(&mut self) -> Result<(), ServerError> {
&mut self,
cookie: KeepAliveCookie,
) -> Result<(), ServerError> {
let msg = Message::ResponseKeepAlive(cookie);
self.send_message(&msg).await?;
self.0 = State::Client;
debug!("sent keepalive response message with cookie {}", cookie);
Ok(())
}
pub async fn keepalive_receive_and_respond(&mut self) -> Result<Option<()>, ServerError> {
match self.recv_message().await? { match self.recv_message().await? {
Message::KeepAlive(cookie) => { Message::KeepAlive(cookie) => {
debug!("received keepalive message with cookie {}", cookie); debug!("received keepalive message with cookie {}", cookie);
self.0 = State::Server(cookie);
self.0 = State::Server; Ok(())
Some(self.send_keepalive_response(cookie).await).transpose()
} }
Message::Done => { Message::Done => {
debug!("client sent done message in keepalive protocol"); debug!("client sent done message in keepalive protocol");
self.0 = State::Done; self.0 = State::Done;
Ok(None) Ok(())
} }
_ => Err(ServerError::InvalidInbound), _ => Err(ServerError::InvalidInbound),
} }
} }
pub async fn send_keepalive_response(&mut self) -> Result<(), ServerError> {
if let State::Server(cookie) = self.state().clone() {
let msg = Message::ResponseKeepAlive(cookie);
self.send_message(&msg).await?;
self.0 = State::Client;
debug!("sent keepalive response message with cookie {}", cookie);
}
Ok(())
}
pub async fn keepalive_roundtrip(&mut self) -> Result<(), ServerError> {
self.recv_keepalive_request().await?;
self.send_keepalive_response().await?;
Ok(())
}
} }