zebra_network/address_book_updater.rs
1//! The address book updater: an actor-style task that owns the address book
2//! write path, wrapped in a [`tower::buffer::Buffer`] service handle.
3
4use std::{
5 cmp::max,
6 net::SocketAddr,
7 sync::Arc,
8 task::{Context, Poll},
9 time::Instant,
10};
11
12use chrono::Utc;
13use futures::future;
14use thiserror::Error;
15use tokio::{
16 sync::{mpsc, oneshot, watch},
17 task::JoinHandle,
18};
19use tower::{buffer::Buffer, util::BoxService, Service};
20use tracing::{Instrument, Level, Span};
21
22use crate::{
23 address_book::AddressMetrics,
24 address_book_peers::AddressBookPeers,
25 meta_addr::{MetaAddr, MetaAddrChange},
26 AddressBook, BanList, BoxError, Config,
27};
28
29#[cfg(test)]
30mod tests;
31
32/// The minimum size of the address book updater channel.
33pub const MIN_CHANNEL_SIZE: usize = 10;
34
35/// The `AddressBookUpdater` hooks into incoming message streams for each peer
36/// and lets the owner of the sender handle update the address book. For
37/// example, it can be used to record per-connection last-seen timestamps, or
38/// add new initial peers to the address book.
39///
40/// It also serves [`AddressBookRequest`]s through a [`Buffer`]-wrapped
41/// [`AddressBookService`], which shares a single ordered request queue with
42/// the change events. This makes compound operations like
43/// [`AddressBookRequest::NextReconnectPeer`] atomic by construction.
44#[derive(Debug, Eq, PartialEq)]
45pub struct AddressBookUpdater;
46
47#[derive(Copy, Clone, Debug, Error, Eq, PartialEq, Hash)]
48#[error("all address book updater senders are closed")]
49pub struct AllAddressBookUpdaterSendersClosed;
50
51/// A request to the address book updater task.
52#[derive(Clone, Debug)]
53pub enum AddressBookRequest {
54 /// Apply a single [`MetaAddrChange`] to the address book.
55 Change(MetaAddrChange),
56
57 /// Extend the address book with a batch of validated gossiped changes.
58 ///
59 /// The address book handles duplicate addresses internally.
60 ExtendGossiped(Vec<MetaAddrChange>),
61
62 /// Atomically choose the next reconnection candidate, and mark it as
63 /// [`AttemptPending`](crate::PeerAddrState::AttemptPending).
64 ///
65 /// Because the address book updater serves one request at a time,
66 /// concurrent `NextReconnectPeer` requests never return the same peer.
67 NextReconnectPeer,
68
69 /// Return the peers that are considered alive, in connection order.
70 //
71 // Hot reads like `getpeerinfo` currently use the shared address book
72 // handle instead of this request, so they stay off the write queue.
73 #[allow(dead_code)]
74 RecentlyLivePeers,
75
76 /// Return the peers that should be written to the peer cache on disk.
77 CacheablePeers,
78
79 /// Return the number of candidate peers that are currently ready for a
80 /// connection attempt.
81 ///
82 /// The returned count is a snapshot: candidates can become ready or be
83 /// attempted by other tasks immediately afterwards.
84 ReadyPeerCount,
85}
86
87/// A response from the address book updater task.
88#[derive(Clone, Debug)]
89pub enum AddressBookResponse {
90 /// The updated address book entry,
91 /// or `None` if the change was rejected or delayed.
92 //
93 // Production changes are fire-and-forget, so the updated entry is
94 // currently only read by tests.
95 Updated(#[allow(dead_code)] Option<MetaAddr>),
96
97 /// The address book was extended with the gossiped changes.
98 Extended,
99
100 /// The next reconnection candidate, already marked as
101 /// [`AttemptPending`](crate::PeerAddrState::AttemptPending),
102 /// or `None` if no peers are ready for a connection attempt.
103 NextReconnectPeer(Option<MetaAddr>),
104
105 /// A list of peers, in response to
106 /// [`RecentlyLivePeers`](AddressBookRequest::RecentlyLivePeers) or
107 /// [`CacheablePeers`](AddressBookRequest::CacheablePeers).
108 Peers(Vec<MetaAddr>),
109
110 /// The number of candidate peers that are currently ready for a
111 /// connection attempt.
112 ReadyPeerCount(usize),
113}
114
115/// A queued call to the address book updater task.
116///
117/// Fire-and-forget change events don't have a response sender.
118#[derive(Debug)]
119pub struct AddressBookCall {
120 /// The request to serve.
121 request: AddressBookRequest,
122
123 /// The channel used to send the response, if the caller wants one.
124 rsp_tx: Option<oneshot::Sender<AddressBookResponse>>,
125}
126
127/// A cheap cloneable handle that sends fire-and-forget [`MetaAddrChange`]
128/// events to the address book updater task.
129///
130/// Changes share the updater's single ordered queue with
131/// [`AddressBookService`] requests.
132#[derive(Clone, Debug)]
133pub struct AddressBookChangeSender(mpsc::Sender<AddressBookCall>);
134
135impl AddressBookChangeSender {
136 /// Sends `change` to the address book updater task,
137 /// waiting until the queue has spare capacity.
138 ///
139 /// Returns an error if the address book updater task has exited.
140 pub async fn send(
141 &self,
142 change: MetaAddrChange,
143 ) -> Result<(), AllAddressBookUpdaterSendersClosed> {
144 self.0
145 .send(AddressBookCall {
146 request: AddressBookRequest::Change(change),
147 rsp_tx: None,
148 })
149 .await
150 .map_err(|_| AllAddressBookUpdaterSendersClosed)
151 }
152}
153
154/// Creates an [`AddressBookChangeSender`] and the receiving half of its
155/// channel, without spawning an updater task.
156///
157/// Used to create stub change senders in tests, and for isolated connections
158/// that don't have an address book.
159pub fn change_channel(size: usize) -> (AddressBookChangeSender, mpsc::Receiver<AddressBookCall>) {
160 let (tx, rx) = mpsc::channel(size);
161 (AddressBookChangeSender(tx), rx)
162}
163
164/// Serves [`AddressBookRequest`]s directly from the address book it holds.
165///
166/// Used both as the inner service behind [`AddressBookService`], and by the
167/// updater task that drains fire-and-forget [`AddressBookChangeSender`] events.
168#[derive(Clone)]
169struct AddressBookHandler {
170 /// The address book to read and update.
171 address_book: Arc<std::sync::Mutex<AddressBook>>,
172
173 /// The channel used to publish the ban list when it changes.
174 bans_sender: Arc<watch::Sender<BanList>>,
175}
176
177impl AddressBookHandler {
178 /// Serves a single `request`.
179 ///
180 /// # Correctness
181 ///
182 /// Briefly holds the address book threaded mutex, with no awaits while it is
183 /// held. External tasks must only use that mutex for hot reads, so this never
184 /// blocks for long.
185 fn handle(&self, request: AddressBookRequest) -> AddressBookResponse {
186 trace!(?request, "got address book request");
187
188 // Every arm needs the address book, so take the lock once, up front. The clock is
189 // then sampled while the lock is already held: sampling it first would let the
190 // timestamps go stale by however long the lock was contended, which makes
191 // `reconnection_peers` under-report how many peers are ready to reconnect.
192 let mut address_book = self
193 .address_book
194 .lock()
195 .expect("mutex should be unpoisoned");
196
197 match request {
198 AddressBookRequest::Change(event) => {
199 let event_ip = event.addr().ip();
200 let updated = address_book.update(event);
201
202 // `UpdateMisbehavior` events should only be passed to `update()` here,
203 // so that this channel is always updated when new addresses are banned.
204 //
205 let bans = updated
206 .is_none()
207 .then(|| address_book.bans())
208 .filter(|bans| bans.is_banned(event_ip));
209
210 // Don't hold the lock while sending the list of `bans`
211 drop(address_book);
212
213 if let Some(bans) = bans {
214 let _ = self.bans_sender.send(bans);
215 }
216
217 AddressBookResponse::Updated(updated)
218 }
219
220 AddressBookRequest::ExtendGossiped(changes) => {
221 // Extend handles duplicate addresses internally.
222 address_book.extend(changes);
223
224 AddressBookResponse::Extended
225 }
226
227 AddressBookRequest::NextReconnectPeer => {
228 // Choose the next candidate, and mark it as `AttemptPending`,
229 // in a single atomic request.
230 let next_peer = address_book
231 .reconnection_peers(Instant::now(), Utc::now())
232 .next()
233 .map(|next_peer| MetaAddr::new_reconnect(next_peer.addr));
234 let next_peer = next_peer.and_then(|change| address_book.update(change));
235
236 AddressBookResponse::NextReconnectPeer(next_peer)
237 }
238
239 AddressBookRequest::RecentlyLivePeers => {
240 AddressBookResponse::Peers(address_book.recently_live_peers(Utc::now()))
241 }
242
243 AddressBookRequest::CacheablePeers => {
244 AddressBookResponse::Peers(address_book.cacheable(Utc::now()))
245 }
246
247 AddressBookRequest::ReadyPeerCount => {
248 let ready_peer_count = address_book
249 .reconnection_peers(Instant::now(), Utc::now())
250 .count();
251
252 AddressBookResponse::ReadyPeerCount(ready_peer_count)
253 }
254 }
255 }
256}
257
258impl Service<AddressBookRequest> for AddressBookHandler {
259 type Response = AddressBookResponse;
260 type Error = BoxError;
261 type Future = future::Ready<Result<Self::Response, Self::Error>>;
262
263 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
264 Poll::Ready(Ok(()))
265 }
266
267 fn call(&mut self, request: AddressBookRequest) -> Self::Future {
268 future::ready(Ok(self.handle(request)))
269 }
270}
271
272/// A shared [`Buffer`]-wrapped service handle to the address book.
273///
274/// Requests are served one at a time, directly from the address book. Writes are
275/// serialised with fire-and-forget [`AddressBookChangeSender`] events by the
276/// address book mutex.
277pub type AddressBookService =
278 Buffer<BoxService<AddressBookRequest, AddressBookResponse, BoxError>, AddressBookRequest>;
279
280impl AddressBookUpdater {
281 /// Spawn a new [`AddressBookUpdater`] task, updating a new [`AddressBook`]
282 /// configured with Zebra's actual `local_listener` address.
283 ///
284 /// Returns handles for:
285 /// - the address book, which should only be used for hot reads,
286 /// - a watch channel for the ban list,
287 /// - the transmission channel for address book update events,
288 /// - a buffered service for address book requests,
289 /// - a watch channel for address book metrics, and
290 /// - the address book updater task join handle.
291 ///
292 /// The task exits with an error when all the returned
293 /// [`AddressBookChangeSender`]s and [`AddressBookService`]s are closed.
294 pub fn spawn(
295 config: &Config,
296 local_listener: SocketAddr,
297 ) -> (
298 Arc<std::sync::Mutex<AddressBook>>,
299 watch::Receiver<BanList>,
300 AddressBookChangeSender,
301 AddressBookService,
302 watch::Receiver<AddressMetrics>,
303 JoinHandle<Result<(), BoxError>>,
304 ) {
305 let address_book = AddressBook::new(
306 local_listener,
307 &config.network,
308 config.max_connections_per_ip,
309 span!(Level::TRACE, "address book"),
310 );
311
312 // Use an update channel and buffer based on the maximum number of
313 // inbound and outbound peers.
314 let channel_size = max(config.peerset_total_connection_limit(), MIN_CHANNEL_SIZE);
315
316 Self::spawn_with_address_book(address_book, channel_size)
317 }
318
319 /// Spawn a new [`AddressBookUpdater`] task for an existing `address_book`,
320 /// using `channel_size` for the update channel and service buffer.
321 ///
322 /// See [`AddressBookUpdater::spawn`] for details.
323 pub fn spawn_with_address_book(
324 address_book: AddressBook,
325 channel_size: usize,
326 ) -> (
327 Arc<std::sync::Mutex<AddressBook>>,
328 watch::Receiver<BanList>,
329 AddressBookChangeSender,
330 AddressBookService,
331 watch::Receiver<AddressMetrics>,
332 JoinHandle<Result<(), BoxError>>,
333 ) {
334 // Create an mpsc channel for both fire-and-forget address book update
335 // events and buffered service requests, so all address book writes go
336 // through a single ordered queue.
337 let (worker_tx, mut worker_rx) = mpsc::channel::<AddressBookCall>(channel_size);
338
339 let address_metrics = address_book.address_metrics_watcher();
340 let address_book = Arc::new(std::sync::Mutex::new(address_book));
341
342 #[cfg(feature = "progress-bar")]
343 let (mut address_info, address_bar, never_bar, failed_bar) = {
344 let address_bar = howudoin::new_root().label("Known Peers");
345 let never_bar =
346 howudoin::new_with_parent(address_bar.id()).label("Never Attempted Peers");
347 let failed_bar = howudoin::new_with_parent(never_bar.id()).label("Failed Peers");
348
349 (address_metrics.clone(), address_bar, never_bar, failed_bar)
350 };
351
352 let (bans_sender, bans_receiver) = tokio::sync::watch::channel(
353 address_book
354 .lock()
355 .expect("mutex should be unpoisoned")
356 .bans(),
357 );
358
359 let handler = AddressBookHandler {
360 address_book: address_book.clone(),
361 bans_sender: Arc::new(bans_sender),
362 };
363 let worker_handler = handler.clone();
364
365 let worker = async move {
366 info!("starting the address book updater");
367
368 while let Some(AddressBookCall { request, rsp_tx }) = worker_rx.recv().await {
369 let response = worker_handler.handle(request);
370
371 if let Some(rsp_tx) = rsp_tx {
372 // The caller might have been cancelled, so ignore send errors.
373 let _ = rsp_tx.send(response);
374 }
375
376 #[cfg(feature = "progress-bar")]
377 if matches!(howudoin::cancelled(), Some(true)) {
378 address_bar.close();
379 never_bar.close();
380 failed_bar.close();
381 } else if address_info.has_changed()? {
382 // We don't track:
383 // - attempt pending because it's always small
384 // - responded because it's the remaining attempted-but-not-failed peers
385 // - recently live because it's similar to the connected peer counts
386
387 let address_info = *address_info.borrow_and_update();
388
389 address_bar
390 .set_pos(u64::try_from(address_info.num_addresses).expect("fits in u64"));
391 // .set_len(u64::try_from(address_info.address_limit).expect("fits in u64"));
392
393 never_bar.set_pos(
394 u64::try_from(address_info.never_attempted_gossiped).expect("fits in u64"),
395 );
396 // .set_len(u64::try_from(address_info.address_limit).expect("fits in u64"));
397
398 failed_bar.set_pos(u64::try_from(address_info.failed).expect("fits in u64"));
399 // .set_len(u64::try_from(address_info.address_limit).expect("fits in u64"));
400 }
401 }
402
403 #[cfg(feature = "progress-bar")]
404 {
405 address_bar.close();
406 never_bar.close();
407 failed_bar.close();
408 }
409
410 let error = Err(AllAddressBookUpdaterSendersClosed.into());
411 info!(?error, "stopping address book updater");
412 error
413 };
414
415 // # Correctness
416 //
417 // The updater task is an async task, not a blocking thread:
418 // - each request only briefly locks the address book mutex, with no
419 // awaits while it is held, so the task never blocks for long, and
420 // - a long-lived blocking task would inhibit auto-advance in tests
421 // that pause the tokio clock.
422 let span = Span::current();
423 let address_book_updater_task_handle = tokio::spawn(worker.instrument(span));
424
425 let change_sender = AddressBookChangeSender(worker_tx.clone());
426
427 let address_book_service = Buffer::new(BoxService::new(handler), channel_size);
428
429 (
430 address_book,
431 bans_receiver,
432 change_sender,
433 address_book_service,
434 address_metrics,
435 address_book_updater_task_handle,
436 )
437 }
438}