Extract `TimestampData` to `AddressBook`.
This allows us to hide the `TimestampCollector` and to expose only the address book data required by the inbound request service. It also lets us have a common data structure (the `AddressBook`) for collecting peer information that can be used to manage information that other peers report to us.
This commit is contained in:
parent
15a698b23c
commit
53be838d51
|
|
@ -0,0 +1,76 @@
|
||||||
|
//! The addressbook manages information about what peers exist, when they were
|
||||||
|
//! seen, and what services they provide.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use futures::channel::mpsc;
|
||||||
|
use tokio::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
constants,
|
||||||
|
types::{MetaAddr, PeerServices},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A database of peers, their advertised services, and information on when they
|
||||||
|
/// were last seen.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct AddressBook {
|
||||||
|
by_addr: HashMap<SocketAddr, (DateTime<Utc>, PeerServices)>,
|
||||||
|
by_time: BTreeMap<DateTime<Utc>, (SocketAddr, PeerServices)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddressBook {
|
||||||
|
/// Update the address book with `event`, a [`MetaAddr`] representing
|
||||||
|
/// observation of a peer.
|
||||||
|
pub fn update(&mut self, event: MetaAddr) {
|
||||||
|
use chrono::Duration as CD;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
?event,
|
||||||
|
data.total = self.by_time.len(),
|
||||||
|
// This would be cleaner if it used "variables" but keeping
|
||||||
|
// it inside the trace! invocation prevents running the range
|
||||||
|
// query unless the output will actually be used.
|
||||||
|
data.recent = self
|
||||||
|
.by_time
|
||||||
|
.range(
|
||||||
|
(Utc::now() - CD::from_std(constants::LIVE_PEER_DURATION).unwrap())..Utc::now()
|
||||||
|
)
|
||||||
|
.count()
|
||||||
|
);
|
||||||
|
|
||||||
|
let MetaAddr {
|
||||||
|
addr,
|
||||||
|
services,
|
||||||
|
last_seen,
|
||||||
|
} = event;
|
||||||
|
|
||||||
|
match self.by_addr.entry(addr) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
let (prev_last_seen, _) = entry.get();
|
||||||
|
// If the new timestamp event is older than the current
|
||||||
|
// one, discard it. This is irrelevant for the timestamp
|
||||||
|
// collector but is important for combining address
|
||||||
|
// information from different peers.
|
||||||
|
if *prev_last_seen > last_seen {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.by_time
|
||||||
|
.remove(prev_last_seen)
|
||||||
|
.expect("cannot have by_addr entry without by_time entry");
|
||||||
|
entry.insert((last_seen, services));
|
||||||
|
self.by_time.insert(last_seen, (addr, services));
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert((last_seen, services));
|
||||||
|
self.by_time.insert(last_seen, (addr, services));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,7 @@ extern crate bitflags;
|
||||||
/// parameterized by 'a), *not* that the object itself has 'static lifetime.
|
/// parameterized by 'a), *not* that the object itself has 'static lifetime.
|
||||||
pub type BoxedStdError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
pub type BoxedStdError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
mod address_book;
|
||||||
mod config;
|
mod config;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod meta_addr;
|
mod meta_addr;
|
||||||
|
|
@ -54,11 +55,10 @@ mod protocol;
|
||||||
mod timestamp_collector;
|
mod timestamp_collector;
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
|
address_book::AddressBook,
|
||||||
config::Config,
|
config::Config,
|
||||||
peer_set::{init, BoxedZebraService},
|
peer_set::{init, BoxedZebraService},
|
||||||
protocol::internal::{Request, Response},
|
protocol::internal::{Request, Response},
|
||||||
// XXX replace with `AddressBook`
|
|
||||||
timestamp_collector::TimestampCollector,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Types used in the definition of [`Request`] and [`Response`] messages.
|
/// Types used in the definition of [`Request`] and [`Response`] messages.
|
||||||
|
|
@ -68,5 +68,5 @@ pub mod types {
|
||||||
|
|
||||||
/// This will be removed when we finish encapsulation
|
/// This will be removed when we finish encapsulation
|
||||||
pub mod should_be_private {
|
pub mod should_be_private {
|
||||||
pub use crate::peer::PeerConnector;
|
pub use crate::{peer::PeerConnector, timestamp_collector::TimestampCollector};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@ mod unready_service;
|
||||||
pub use discover::PeerDiscover;
|
pub use discover::PeerDiscover;
|
||||||
pub use set::PeerSet;
|
pub use set::PeerSet;
|
||||||
|
|
||||||
use std::{net::SocketAddr, pin::Pin};
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
pin::Pin,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
|
|
@ -29,11 +33,9 @@ use tracing::Level;
|
||||||
use tracing_futures::Instrument;
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
|
||||||
peer::{HandshakeError, PeerClient, PeerConnector},
|
peer::{HandshakeError, PeerClient, PeerConnector},
|
||||||
protocol::internal::{Request, Response},
|
|
||||||
timestamp_collector::TimestampCollector,
|
timestamp_collector::TimestampCollector,
|
||||||
BoxedStdError,
|
AddressBook, BoxedStdError, Config, Request, Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A type alias for a boxed [`tower::Service`] used to process [`Request`]s into [`Response`]s.
|
/// A type alias for a boxed [`tower::Service`] used to process [`Request`]s into [`Response`]s.
|
||||||
|
|
@ -50,7 +52,7 @@ pub type BoxedZebraService = Box<
|
||||||
type PeerChange = Result<Change<SocketAddr, PeerClient>, BoxedStdError>;
|
type PeerChange = Result<Change<SocketAddr, PeerClient>, BoxedStdError>;
|
||||||
|
|
||||||
/// Initialize a peer set with the given `config`, forwarding peer requests to the `inbound_service`.
|
/// Initialize a peer set with the given `config`, forwarding peer requests to the `inbound_service`.
|
||||||
pub fn init<S>(config: Config, inbound_service: S) -> (BoxedZebraService, TimestampCollector)
|
pub fn init<S>(config: Config, inbound_service: S) -> (BoxedZebraService, Arc<Mutex<AddressBook>>)
|
||||||
where
|
where
|
||||||
S: Service<Request, Response = Response, Error = BoxedStdError> + Clone + Send + 'static,
|
S: Service<Request, Response = Response, Error = BoxedStdError> + Clone + Send + 'static,
|
||||||
S::Future: Send + 'static,
|
S::Future: Send + 'static,
|
||||||
|
|
@ -96,7 +98,7 @@ where
|
||||||
|
|
||||||
// 3. Outgoing peers we connect to in response to load.
|
// 3. Outgoing peers we connect to in response to load.
|
||||||
|
|
||||||
(Box::new(peer_set), timestamp_collector)
|
(Box::new(peer_set), timestamp_collector.address_book())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use the provided `peer_connector` to connect to `initial_peers`, then send
|
/// Use the provided `peer_connector` to connect to `initial_peers`, then send
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//! Management of peer liveness / last-seen information.
|
//! The timestamp collector collects liveness information from peers.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
|
|
@ -13,9 +13,11 @@ use tokio::prelude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
constants,
|
constants,
|
||||||
types::{MetaAddr, PeerServices},
|
types::{MetaAddr, PeerServices},
|
||||||
|
AddressBook,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Maintains a lookup table from peer addresses to last-seen times.
|
/// The timestamp collector hooks into incoming message streams for each peer and
|
||||||
|
/// records per-connection last-seen timestamps into an [`AddressBook`].
|
||||||
///
|
///
|
||||||
/// On creation, the `TimestampCollector` spawns a worker task to process new
|
/// On creation, the `TimestampCollector` spawns a worker task to process new
|
||||||
/// timestamp events. The resulting `TimestampCollector` can be cloned, and the
|
/// timestamp events. The resulting `TimestampCollector` can be cloned, and the
|
||||||
|
|
@ -27,70 +29,15 @@ pub struct TimestampCollector {
|
||||||
// We do not expect mutex contention to be a problem, because
|
// We do not expect mutex contention to be a problem, because
|
||||||
// the dominant accessor is the collector worker, and it has a long
|
// the dominant accessor is the collector worker, and it has a long
|
||||||
// event buffer to hide latency if other tasks block it temporarily.
|
// event buffer to hide latency if other tasks block it temporarily.
|
||||||
data: Arc<Mutex<TimestampData>>,
|
data: Arc<Mutex<AddressBook>>,
|
||||||
shutdown: Arc<ShutdownSignal>,
|
shutdown: Arc<ShutdownSignal>,
|
||||||
worker_tx: mpsc::Sender<MetaAddr>,
|
worker_tx: mpsc::Sender<MetaAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
struct TimestampData {
|
|
||||||
by_addr: HashMap<SocketAddr, (DateTime<Utc>, PeerServices)>,
|
|
||||||
by_time: BTreeMap<DateTime<Utc>, (SocketAddr, PeerServices)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimestampData {
|
|
||||||
fn update(&mut self, event: MetaAddr) {
|
|
||||||
use chrono::Duration as CD;
|
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
|
|
||||||
trace!(
|
|
||||||
?event,
|
|
||||||
data.total = self.by_time.len(),
|
|
||||||
// This would be cleaner if it used "variables" but keeping
|
|
||||||
// it inside the trace! invocation prevents running the range
|
|
||||||
// query unless the output will actually be used.
|
|
||||||
data.recent = self
|
|
||||||
.by_time
|
|
||||||
.range(
|
|
||||||
(Utc::now() - CD::from_std(constants::LIVE_PEER_DURATION).unwrap())..Utc::now()
|
|
||||||
)
|
|
||||||
.count()
|
|
||||||
);
|
|
||||||
|
|
||||||
let MetaAddr {
|
|
||||||
addr,
|
|
||||||
services,
|
|
||||||
last_seen,
|
|
||||||
} = event;
|
|
||||||
|
|
||||||
match self.by_addr.entry(addr) {
|
|
||||||
Entry::Occupied(mut entry) => {
|
|
||||||
let (prev_last_seen, _) = entry.get();
|
|
||||||
// If the new timestamp event is older than the current
|
|
||||||
// one, discard it. This is irrelevant for the timestamp
|
|
||||||
// collector but is important for combining address
|
|
||||||
// information from different peers.
|
|
||||||
if *prev_last_seen > last_seen {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.by_time
|
|
||||||
.remove(prev_last_seen)
|
|
||||||
.expect("cannot have by_addr entry without by_time entry");
|
|
||||||
entry.insert((last_seen, services));
|
|
||||||
self.by_time.insert(last_seen, (addr, services));
|
|
||||||
}
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert((last_seen, services));
|
|
||||||
self.by_time.insert(last_seen, (addr, services));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimestampCollector {
|
impl TimestampCollector {
|
||||||
/// Constructs a new `TimestampCollector`, spawning a worker task to process updates.
|
/// Constructs a new `TimestampCollector`, spawning a worker task to process updates.
|
||||||
pub fn new() -> TimestampCollector {
|
pub fn new() -> TimestampCollector {
|
||||||
let data = Arc::new(Mutex::new(TimestampData::default()));
|
let data = Arc::new(Mutex::new(AddressBook::default()));
|
||||||
// We need to make a copy now so we can move data into the async block.
|
// We need to make a copy now so we can move data into the async block.
|
||||||
let data2 = data.clone();
|
let data2 = data.clone();
|
||||||
|
|
||||||
|
|
@ -121,6 +68,11 @@ impl TimestampCollector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a shared reference to the [`AddressBook`] this collector updates.
|
||||||
|
pub fn address_book(&self) -> Arc<Mutex<AddressBook>> {
|
||||||
|
self.data.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn sender_handle(&self) -> mpsc::Sender<MetaAddr> {
|
pub(crate) fn sender_handle(&self) -> mpsc::Sender<MetaAddr> {
|
||||||
self.worker_tx.clone()
|
self.worker_tx.clone()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl Runnable for ConnectCmd {
|
||||||
|
|
||||||
impl ConnectCmd {
|
impl ConnectCmd {
|
||||||
async fn connect(&self) -> Result<(), failure::Error> {
|
async fn connect(&self) -> Result<(), failure::Error> {
|
||||||
use zebra_network::{Request, Response, TimestampCollector};
|
use zebra_network::{Request, Response};
|
||||||
|
|
||||||
info!("begin tower-based peer handling test stub");
|
info!("begin tower-based peer handling test stub");
|
||||||
use tower::{buffer::Buffer, service_fn, Service, ServiceExt};
|
use tower::{buffer::Buffer, service_fn, Service, ServiceExt};
|
||||||
|
|
@ -77,7 +77,7 @@ impl ConnectCmd {
|
||||||
// Later, this should turn into initial_peers = vec![self.addr];
|
// Later, this should turn into initial_peers = vec![self.addr];
|
||||||
config.initial_peers = {
|
config.initial_peers = {
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use zebra_network::should_be_private::PeerConnector;
|
use zebra_network::should_be_private::{PeerConnector, TimestampCollector};
|
||||||
|
|
||||||
let collector = TimestampCollector::new();
|
let collector = TimestampCollector::new();
|
||||||
let mut pc = Buffer::new(
|
let mut pc = Buffer::new(
|
||||||
|
|
@ -108,7 +108,7 @@ impl ConnectCmd {
|
||||||
addrs.into_iter().map(|meta| meta.addr).collect::<Vec<_>>()
|
addrs.into_iter().map(|meta| meta.addr).collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut peer_set, _tc) = zebra_network::init(config, node);
|
let (mut peer_set, _address_book) = zebra_network::init(config, node);
|
||||||
|
|
||||||
info!("waiting for peer_set ready");
|
info!("waiting for peer_set ready");
|
||||||
peer_set.ready().await.map_err(Error::from_boxed_compat)?;
|
peer_set.ready().await.map_err(Error::from_boxed_compat)?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue