Skip to main content

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}