use crate::{
Error,
model::RecordPackage,
peer::{Answer, CALL_TIMEOUT, Command, request},
peer_gossip::Shared,
peer_protocol::Kind,
peerbook::PeerAddress,
retrieval::RecordSource,
};
use bitcoin::Txid;
use libp2p::{Multiaddr, PeerId};
use std::{
sync::{
Arc,
atomic::{AtomicBool, AtomicUsize, Ordering},
mpsc,
},
time::Instant,
};
use tokio::sync::mpsc as async_mpsc;
#[derive(Clone)]
pub(crate) struct Link {
pub(crate) commands: async_mpsc::Sender<Command>,
pub(crate) stop: Arc<AtomicBool>,
pub(crate) shared: Arc<Shared>,
}
impl Link {
pub(crate) fn source(&self, peer: PeerId, addresses: Vec<Multiaddr>) -> PeerSource {
PeerSource {
peer,
addresses,
link: self.clone(),
}
}
}
#[derive(Clone)]
pub struct PeerSource {
peer: PeerId,
addresses: Vec<Multiaddr>,
link: Link,
}
impl PeerSource {
pub(crate) fn request<T>(
&self,
kind: Kind,
limit: Instant,
answer: fn(mpsc::SyncSender<Result<T, Error>>) -> Answer,
) -> Result<T, Error> {
let target = (self.peer, self.addresses.clone());
request(
&self.link.commands,
&self.link.stop,
target,
kind,
limit,
answer,
)
}
pub(crate) fn connected(&self) -> bool {
self.link.shared.is_connected(self.peer)
}
pub(crate) fn outbound(&self) -> bool {
self.link.shared.is_outbound(self.peer)
}
pub(crate) fn configured(&self) -> bool {
!self.addresses.is_empty()
}
pub(crate) fn disconnect(&self) {
let _ = self.link.commands.try_send(Command::Disconnect(self.peer));
}
}
impl RecordSource for PeerSource {
fn fetch(&self, txid: Txid) -> Result<Option<RecordPackage>, Error> {
self.fetch_until(txid, Instant::now() + CALL_TIMEOUT)
}
}
#[derive(Clone)]
pub struct SyncPeers {
link: Link,
manual: Vec<PeerAddress>,
next: Arc<AtomicUsize>,
next_inbound: Arc<AtomicUsize>,
}
impl SyncPeers {
pub const RECORD_PEERS: usize = 4;
pub(crate) fn new(link: Link, manual: Vec<PeerAddress>) -> Self {
Self {
link,
manual,
next: Arc::new(AtomicUsize::new(0)),
next_inbound: Arc::new(AtomicUsize::new(0)),
}
}
pub fn sources(&self, omitting: &[String]) -> Vec<(String, PeerSource)> {
let configured = self
.manual
.iter()
.map(|address| (address.peer_id(), vec![address.address().clone()]));
let live = self
.link
.shared
.live()
.into_iter()
.map(|peer| (peer, Vec::new()));
let mut sources: Vec<(String, PeerSource)> = Vec::new();
for (peer, addresses) in configured.chain(live) {
let label = peer.to_string();
if omitting.contains(&label) || sources.iter().any(|(known, _)| *known == label) {
continue;
}
sources.push((label, self.link.source(peer, addresses)));
}
sources
}
pub(crate) fn window(&self, size: usize) -> Vec<PeerSource> {
let peers = self.sources(&[]);
let (configured, live): (Vec<PeerSource>, Vec<PeerSource>) = peers
.into_iter()
.map(|(_, source)| source)
.partition(PeerSource::configured);
let (outbound, inbound): (Vec<PeerSource>, Vec<PeerSource>) =
live.into_iter().partition(PeerSource::outbound);
let mut window: Vec<PeerSource> = configured.into_iter().take(size).collect();
for (group, next) in [(&outbound, &self.next), (&inbound, &self.next_inbound)] {
let room = size.saturating_sub(window.len()).min(group.len());
if room > 0 {
let start = next.fetch_add(room, Ordering::Relaxed) % group.len();
window
.extend((0..room).map(|offset| group[(start + offset) % group.len()].clone()));
}
}
window
}
pub fn record_sources(&self) -> Vec<PeerSource> {
self.window(Self::RECORD_PEERS)
}
pub fn record_count(&self) -> usize {
self.sources(&[]).len().min(Self::RECORD_PEERS)
}
}