use std::{
cmp::max,
net::SocketAddr,
sync::Arc,
task::{Context, Poll},
time::Instant,
};
use chrono::Utc;
use futures::future;
use thiserror::Error;
use tokio::{
sync::{mpsc, oneshot, watch},
task::JoinHandle,
};
use tower::{buffer::Buffer, util::BoxService, Service};
use tracing::{Instrument, Level, Span};
use crate::{
address_book::AddressMetrics,
address_book_peers::AddressBookPeers,
meta_addr::{MetaAddr, MetaAddrChange},
AddressBook, BanList, BoxError, Config,
};
#[cfg(test)]
mod tests;
pub const MIN_CHANNEL_SIZE: usize = 10;
#[derive(Debug, Eq, PartialEq)]
pub struct AddressBookUpdater;
#[derive(Copy, Clone, Debug, Error, Eq, PartialEq, Hash)]
#[error("all address book updater senders are closed")]
pub struct AllAddressBookUpdaterSendersClosed;
#[derive(Clone, Debug)]
pub enum AddressBookRequest {
Change(MetaAddrChange),
ExtendGossiped(Vec<MetaAddrChange>),
NextReconnectPeer,
#[allow(dead_code)]
RecentlyLivePeers,
CacheablePeers,
ReadyPeerCount,
}
#[derive(Clone, Debug)]
pub enum AddressBookResponse {
Updated(#[allow(dead_code)] Option<MetaAddr>),
Extended,
NextReconnectPeer(Option<MetaAddr>),
Peers(Vec<MetaAddr>),
ReadyPeerCount(usize),
}
#[derive(Debug)]
pub struct AddressBookCall {
request: AddressBookRequest,
rsp_tx: Option<oneshot::Sender<AddressBookResponse>>,
}
#[derive(Clone, Debug)]
pub struct AddressBookChangeSender(mpsc::Sender<AddressBookCall>);
impl AddressBookChangeSender {
pub async fn send(
&self,
change: MetaAddrChange,
) -> Result<(), AllAddressBookUpdaterSendersClosed> {
self.0
.send(AddressBookCall {
request: AddressBookRequest::Change(change),
rsp_tx: None,
})
.await
.map_err(|_| AllAddressBookUpdaterSendersClosed)
}
}
pub fn change_channel(size: usize) -> (AddressBookChangeSender, mpsc::Receiver<AddressBookCall>) {
let (tx, rx) = mpsc::channel(size);
(AddressBookChangeSender(tx), rx)
}
#[derive(Clone)]
struct AddressBookHandler {
address_book: Arc<std::sync::Mutex<AddressBook>>,
bans_sender: Arc<watch::Sender<BanList>>,
}
impl AddressBookHandler {
fn handle(&self, request: AddressBookRequest) -> AddressBookResponse {
trace!(?request, "got address book request");
let mut address_book = self
.address_book
.lock()
.expect("mutex should be unpoisoned");
match request {
AddressBookRequest::Change(event) => {
let event_ip = event.addr().ip();
let updated = address_book.update(event);
let bans = updated
.is_none()
.then(|| address_book.bans())
.filter(|bans| bans.is_banned(event_ip));
drop(address_book);
if let Some(bans) = bans {
let _ = self.bans_sender.send(bans);
}
AddressBookResponse::Updated(updated)
}
AddressBookRequest::ExtendGossiped(changes) => {
address_book.extend(changes);
AddressBookResponse::Extended
}
AddressBookRequest::NextReconnectPeer => {
let next_peer = address_book
.reconnection_peers(Instant::now(), Utc::now())
.next()
.map(|next_peer| MetaAddr::new_reconnect(next_peer.addr));
let next_peer = next_peer.and_then(|change| address_book.update(change));
AddressBookResponse::NextReconnectPeer(next_peer)
}
AddressBookRequest::RecentlyLivePeers => {
AddressBookResponse::Peers(address_book.recently_live_peers(Utc::now()))
}
AddressBookRequest::CacheablePeers => {
AddressBookResponse::Peers(address_book.cacheable(Utc::now()))
}
AddressBookRequest::ReadyPeerCount => {
let ready_peer_count = address_book
.reconnection_peers(Instant::now(), Utc::now())
.count();
AddressBookResponse::ReadyPeerCount(ready_peer_count)
}
}
}
}
impl Service<AddressBookRequest> for AddressBookHandler {
type Response = AddressBookResponse;
type Error = BoxError;
type Future = future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, request: AddressBookRequest) -> Self::Future {
future::ready(Ok(self.handle(request)))
}
}
pub type AddressBookService =
Buffer<BoxService<AddressBookRequest, AddressBookResponse, BoxError>, AddressBookRequest>;
impl AddressBookUpdater {
pub fn spawn(
config: &Config,
local_listener: SocketAddr,
) -> (
Arc<std::sync::Mutex<AddressBook>>,
watch::Receiver<BanList>,
AddressBookChangeSender,
AddressBookService,
watch::Receiver<AddressMetrics>,
JoinHandle<Result<(), BoxError>>,
) {
let address_book = AddressBook::new(
local_listener,
&config.network,
config.max_connections_per_ip,
span!(Level::TRACE, "address book"),
);
let channel_size = max(config.peerset_total_connection_limit(), MIN_CHANNEL_SIZE);
Self::spawn_with_address_book(address_book, channel_size)
}
pub fn spawn_with_address_book(
address_book: AddressBook,
channel_size: usize,
) -> (
Arc<std::sync::Mutex<AddressBook>>,
watch::Receiver<BanList>,
AddressBookChangeSender,
AddressBookService,
watch::Receiver<AddressMetrics>,
JoinHandle<Result<(), BoxError>>,
) {
let (worker_tx, mut worker_rx) = mpsc::channel::<AddressBookCall>(channel_size);
let address_metrics = address_book.address_metrics_watcher();
let address_book = Arc::new(std::sync::Mutex::new(address_book));
#[cfg(feature = "progress-bar")]
let (mut address_info, address_bar, never_bar, failed_bar) = {
let address_bar = howudoin::new_root().label("Known Peers");
let never_bar =
howudoin::new_with_parent(address_bar.id()).label("Never Attempted Peers");
let failed_bar = howudoin::new_with_parent(never_bar.id()).label("Failed Peers");
(address_metrics.clone(), address_bar, never_bar, failed_bar)
};
let (bans_sender, bans_receiver) = tokio::sync::watch::channel(
address_book
.lock()
.expect("mutex should be unpoisoned")
.bans(),
);
let handler = AddressBookHandler {
address_book: address_book.clone(),
bans_sender: Arc::new(bans_sender),
};
let worker_handler = handler.clone();
let worker = async move {
info!("starting the address book updater");
while let Some(AddressBookCall { request, rsp_tx }) = worker_rx.recv().await {
let response = worker_handler.handle(request);
if let Some(rsp_tx) = rsp_tx {
let _ = rsp_tx.send(response);
}
#[cfg(feature = "progress-bar")]
if matches!(howudoin::cancelled(), Some(true)) {
address_bar.close();
never_bar.close();
failed_bar.close();
} else if address_info.has_changed()? {
let address_info = *address_info.borrow_and_update();
address_bar
.set_pos(u64::try_from(address_info.num_addresses).expect("fits in u64"));
never_bar.set_pos(
u64::try_from(address_info.never_attempted_gossiped).expect("fits in u64"),
);
failed_bar.set_pos(u64::try_from(address_info.failed).expect("fits in u64"));
}
}
#[cfg(feature = "progress-bar")]
{
address_bar.close();
never_bar.close();
failed_bar.close();
}
let error = Err(AllAddressBookUpdaterSendersClosed.into());
info!(?error, "stopping address book updater");
error
};
let span = Span::current();
let address_book_updater_task_handle = tokio::spawn(worker.instrument(span));
let change_sender = AddressBookChangeSender(worker_tx.clone());
let address_book_service = Buffer::new(BoxService::new(handler), channel_size);
(
address_book,
bans_receiver,
change_sender,
address_book_service,
address_metrics,
address_book_updater_task_handle,
)
}
}