Skip to main content

tor_guardmgr/
vanguards.rs

1//! Experimental support for vanguards.
2//!
3//! For more information, see the [vanguards spec].
4//!
5//! [vanguards spec]: https://spec.torproject.org/vanguards-spec/index.html.
6
7pub mod config;
8mod err;
9mod set;
10
11use std::sync::{Arc, RwLock, Weak};
12use std::time::{Duration, SystemTime};
13
14use futures::stream::BoxStream;
15use futures::{FutureExt as _, future};
16use futures::{StreamExt as _, select_biased};
17use postage::stream::Stream as _;
18use postage::watch;
19use tor_rtcompat::SpawnExt as _;
20
21use tor_async_utils::PostageWatchSenderExt as _;
22use tor_config::ReconfigureError;
23use tor_error::{error_report, internal, into_internal};
24use tor_netdir::{DirEvent, NetDir, NetDirProvider, Timeliness};
25use tor_persist::{DynStorageHandle, StateMgr};
26use tor_relay_selection::RelaySelector;
27use tor_rtcompat::Runtime;
28use tracing::{debug, info, instrument};
29
30use crate::{RetireCircuits, VanguardMode};
31
32use set::VanguardSets;
33
34use crate::VanguardConfig;
35pub use config::VanguardParams;
36pub use err::VanguardMgrError;
37pub use set::Vanguard;
38
39/// The key used for storing the vanguard sets to persistent storage using `StateMgr`.
40const STORAGE_KEY: &str = "vanguards";
41
42/// The vanguard manager.
43pub struct VanguardMgr<R: Runtime> {
44    /// The mutable state.
45    inner: RwLock<Inner>,
46    /// The runtime.
47    runtime: R,
48    /// The persistent storage handle, used for writing the vanguard sets to disk
49    /// if full vanguards are enabled.
50    storage: DynStorageHandle<VanguardSets>,
51}
52
53/// The mutable inner state of [`VanguardMgr`].
54struct Inner {
55    /// The current vanguard parameters.
56    params: VanguardParams,
57    /// Whether to use full, lite, or no vanguards.
58    ///
59    // TODO(#1382): we should derive the mode from the
60    // vanguards-enabled and vanguards-hs-service consensus params.
61    mode: VanguardMode,
62    /// The L2 and L3 vanguards.
63    ///
64    /// The L3 vanguards are only used if we are running in
65    /// [`Full`](VanguardMode::Full) vanguard mode.
66    /// Otherwise, the L3 set is not populated, or read from.
67    ///
68    /// If [`Full`](VanguardMode::Full) vanguard mode is enabled,
69    /// the vanguard sets will be persisted to disk whenever
70    /// vanuards are rotated, added, or removed.
71    ///
72    /// The vanguard sets are updated and persisted to storage by
73    /// [`update_vanguard_sets`](Inner::update_vanguard_sets).
74    ///
75    /// If the `VanguardSets` change while we are in "lite" mode,
76    /// the changes will not *not* be written to storage.
77    /// If we later switch to "full" vanguards, those previous changes still
78    /// won't be persisted to storage: we only flush to storage if the
79    /// [`VanguardSets`] change *while* we are in "full" mode
80    /// (changing the [`VanguardMode`] does not constitute a change in the `VanguardSets`).
81    //
82    // TODO HS-VANGUARDS: the correct behaviour here might be to never switch back to lite mode
83    // after enabling full vanguards. If we do that, persisting the vanguard sets will be simpler,
84    // as we can just unconditionally flush to storage if the vanguard mode is switched to full.
85    // Right now we can't do that, because we don't remember the "mode":
86    // we derive it on the fly from `has_onion_svc` and the current `VanguardParams`.
87    //
88    ///
89    /// This is initialized with the vanguard sets read from the vanguard state file,
90    /// if the file exists, or with a [`Default`] `VanguardSets`, if it doesn't.
91    ///
92    /// Note: The `VanguardSets` are read from the vanguard state file
93    /// even if full vanguards are not enabled. They are *not*, however, written
94    /// to the state file unless full vanguards are in use.
95    vanguard_sets: VanguardSets,
96    /// Whether we're running an onion service.
97    ///
98    // TODO(#1382): This should be used for deciding whether to use the `vanguards_hs_service` or the
99    // `vanguards_enabled` [`NetParameter`](tor_netdir::params::NetParameters).
100    #[allow(unused)]
101    has_onion_svc: bool,
102    /// A channel for sending VanguardConfig changes to the vanguard maintenance task.
103    config_tx: watch::Sender<VanguardConfig>,
104}
105
106/// Whether the [`VanguardMgr::maintain_vanguard_sets`] task
107/// should continue running or shut down.
108///
109/// Returned from [`VanguardMgr::run_once`].
110#[derive(Copy, Clone, Debug)]
111enum ShutdownStatus {
112    /// Continue calling `run_once`.
113    Continue,
114    /// The `VanguardMgr` was dropped, terminate the task.
115    Terminate,
116}
117
118impl<R: Runtime> VanguardMgr<R> {
119    /// Create a new `VanguardMgr`.
120    ///
121    /// The `state_mgr` handle is used for persisting the "vanguards-full" guard pools to disk.
122    pub fn new<S>(
123        config: &VanguardConfig,
124        runtime: R,
125        state_mgr: S,
126        has_onion_svc: bool,
127    ) -> Result<Self, VanguardMgrError>
128    where
129        S: StateMgr + Send + Sync + 'static,
130    {
131        // Note: we start out with default vanguard params, but we adjust them
132        // as soon as we obtain a NetDir (see Self::run_once()).
133        let params = VanguardParams::default();
134        let storage: DynStorageHandle<VanguardSets> = state_mgr.create_handle(STORAGE_KEY);
135
136        let vanguard_sets = match storage.load()? {
137            Some(mut sets) => {
138                info!("Loading vanguards from vanguard state file");
139                // Discard the now-expired the vanguards
140                let now = runtime.wallclock();
141                let _ = sets.remove_expired(now);
142                sets
143            }
144            None => {
145                debug!("Vanguard state file not found, selecting new vanguards");
146                // Initially, all sets have a target size of 0.
147                // This is OK because the target is only used for repopulating the vanguard sets,
148                // and we can't repopulate the sets without a netdir.
149                // The target gets adjusted once we obtain a netdir.
150                Default::default()
151            }
152        };
153
154        let (config_tx, _config_rx) = watch::channel();
155        let inner = Inner {
156            params,
157            mode: config.mode(),
158            vanguard_sets,
159            has_onion_svc,
160            config_tx,
161        };
162
163        Ok(Self {
164            inner: RwLock::new(inner),
165            runtime,
166            storage,
167        })
168    }
169
170    /// Launch the vanguard pool management tasks.
171    ///
172    /// These run until the `VanguardMgr` is dropped.
173    //
174    // This spawns [`VanguardMgr::maintain_vanguard_sets`].
175    #[instrument(level = "trace", skip_all)]
176    pub fn launch_background_tasks(
177        self: &Arc<Self>,
178        netdir_provider: &Arc<dyn NetDirProvider>,
179    ) -> Result<(), VanguardMgrError>
180    where
181        R: Runtime,
182    {
183        let netdir_provider = Arc::clone(netdir_provider);
184        let config_rx = self
185            .inner
186            .write()
187            .expect("poisoned lock")
188            .config_tx
189            .subscribe();
190        self.runtime
191            .spawn(Self::maintain_vanguard_sets(
192                Arc::downgrade(self),
193                Arc::downgrade(&netdir_provider),
194                config_rx,
195            ))
196            .map_err(|e| VanguardMgrError::Spawn(Arc::new(e)))?;
197
198        Ok(())
199    }
200
201    /// Replace the configuration in this `VanguardMgr` with the specified `config`.
202    pub fn reconfigure(&self, config: &VanguardConfig) -> Result<RetireCircuits, ReconfigureError> {
203        // TODO(#1382): abolish VanguardConfig and derive the mode from the VanguardParams
204        // and has_onion_svc instead.
205        //
206        // TODO(#1382): update has_onion_svc if the new config enables onion svc usage
207        //
208        // Perhaps we should always escalate to Full if we start running an onion service,
209        // but not decessarily downgrade to lite if we stop.
210        // See <https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/2083#note_3018173>
211        let mut inner = self.inner.write().expect("poisoned lock");
212        let new_mode = config.mode();
213        if new_mode != inner.mode {
214            inner.mode = new_mode;
215
216            // Wake up the maintenance task to replenish the vanguard pools.
217            inner.config_tx.maybe_send(|_| config.clone());
218
219            Ok(RetireCircuits::All)
220        } else {
221            Ok(RetireCircuits::None)
222        }
223    }
224
225    /// Return a [`Vanguard`] relay for use in the specified layer.
226    ///
227    /// The `relay_selector` must exclude the relays that would neighbor this vanguard
228    /// in the path.
229    ///
230    /// Specifically, it should exclude
231    ///   * the last relay in the path (the one immediately preceding the vanguard): the same relay
232    ///     cannot be used in consecutive positions in the path (a relay won't let you extend the
233    ///     circuit to itself).
234    ///   * the penultimate relay of the path, if there is one: relays don't allow extending the
235    ///     circuit to their previous hop
236    ///
237    /// If [`Full`](VanguardMode::Full) vanguards are in use, this function can be used
238    /// for selecting both [`Layer2`](Layer::Layer2) and [`Layer3`](Layer::Layer3) vanguards.
239    ///
240    /// If [`Lite`](VanguardMode::Lite) vanguards are in use, this function can only be used
241    /// for selecting [`Layer2`](Layer::Layer2) vanguards.
242    /// It will return an error if a [`Layer3`](Layer::Layer3) is requested.
243    ///
244    /// Returns an error if vanguards are disabled.
245    ///
246    /// Returns a [`NoSuitableRelay`](VanguardMgrError::NoSuitableRelay) error
247    /// if none of our vanguards satisfy the `layer` and `neighbor_exclusion` requirements.
248    ///
249    /// Returns a [`BootstrapRequired`](VanguardMgrError::BootstrapRequired) error
250    /// if called before the vanguard manager has finished bootstrapping,
251    /// or if all the vanguards have become unusable
252    /// (by expiring or no longer being listed in the consensus)
253    /// and we are unable to replenish them.
254    ///
255    ///  ### Example
256    ///
257    ///  If the partially built path is of the form `G - L2` and we are selecting the L3 vanguard,
258    ///  the `RelayExclusion` should contain `G` and `L2` (to prevent building a path of the form
259    ///  `G - L2 - G`, or `G - L2 - L2`).
260    ///
261    ///  If the path only contains the L1 guard (`G`), then the `RelayExclusion` should only
262    ///  exclude `G`.
263    pub fn select_vanguard<'a, Rng: rand::Rng>(
264        &self,
265        rng: &mut Rng,
266        netdir: &'a NetDir,
267        layer: Layer,
268        relay_selector: &RelaySelector<'a>,
269    ) -> Result<Vanguard<'a>, VanguardMgrError> {
270        use VanguardMode::*;
271
272        let inner = self.inner.read().expect("poisoned lock");
273
274        // All our vanguard sets are empty. This means select_vanguards() was called before
275        // maintain_vanguard_sets() managed to obtain a netdir and populate the vanguard sets,
276        // or all the vanguards have become unusable and we have been unable to replenish them.
277        if inner.vanguard_sets.l2().is_empty() && inner.vanguard_sets.l3().is_empty() {
278            return Err(VanguardMgrError::BootstrapRequired {
279                action: "select vanguard",
280            });
281        }
282
283        let relay =
284            match (layer, inner.mode) {
285                (Layer::Layer2, Full) | (Layer::Layer2, Lite) => inner
286                    .vanguard_sets
287                    .l2()
288                    .pick_relay(rng, netdir, relay_selector),
289                (Layer::Layer3, Full) => {
290                    inner
291                        .vanguard_sets
292                        .l3()
293                        .pick_relay(rng, netdir, relay_selector)
294                }
295                _ => {
296                    return Err(VanguardMgrError::LayerNotSupported {
297                        layer,
298                        mode: inner.mode,
299                    });
300                }
301            };
302
303        relay.ok_or(VanguardMgrError::NoSuitableRelay(layer))
304    }
305
306    /// The vanguard set management task.
307    ///
308    /// This is a background task that:
309    /// * removes vanguards from the L2 and L3 vanguard sets when they expire
310    /// * ensures the vanguard sets are repopulated with new vanguards
311    ///   when the number of vanguards drops below a certain threshold
312    /// * handles `NetDir` changes, updating the vanguard set sizes as needed
313    async fn maintain_vanguard_sets(
314        mgr: Weak<Self>,
315        netdir_provider: Weak<dyn NetDirProvider>,
316        mut config_rx: watch::Receiver<VanguardConfig>,
317    ) {
318        let mut netdir_events = match netdir_provider.upgrade() {
319            Some(provider) => provider.events(),
320            None => {
321                return;
322            }
323        };
324
325        loop {
326            match Self::run_once(
327                Weak::clone(&mgr),
328                Weak::clone(&netdir_provider),
329                &mut netdir_events,
330                &mut config_rx,
331            )
332            .await
333            {
334                Ok(ShutdownStatus::Continue) => continue,
335                Ok(ShutdownStatus::Terminate) => {
336                    debug!("Vanguard manager is shutting down");
337                    break;
338                }
339                Err(e) => {
340                    error_report!(e, "Vanguard manager crashed");
341                    break;
342                }
343            }
344        }
345    }
346
347    /// Wait until a vanguard expires or until there is a new [`NetDir`].
348    ///
349    /// This populates the L2 and L3 vanguard sets,
350    /// and rotates the vanguards when their lifetime expires.
351    ///
352    /// Note: the L3 set is only populated with vanguards if
353    /// [`Full`](VanguardMode::Full) vanguards are enabled.
354    async fn run_once(
355        mgr: Weak<Self>,
356        netdir_provider: Weak<dyn NetDirProvider>,
357        netdir_events: &mut BoxStream<'static, DirEvent>,
358        config_rx: &mut watch::Receiver<VanguardConfig>,
359    ) -> Result<ShutdownStatus, VanguardMgrError> {
360        let (mgr, netdir_provider) = match (mgr.upgrade(), netdir_provider.upgrade()) {
361            (Some(mgr), Some(netdir_provider)) => (mgr, netdir_provider),
362            _ => return Ok(ShutdownStatus::Terminate),
363        };
364
365        let now = mgr.runtime.wallclock();
366        let next_to_expire = mgr.rotate_expired(&netdir_provider, now)?;
367        // A future that sleeps until the next vanguard expires
368        let sleep_fut = async {
369            if let Some(dur) = next_to_expire {
370                let () = mgr.runtime.sleep(dur).await;
371            } else {
372                future::pending::<()>().await;
373            }
374        };
375
376        select_biased! {
377            event = netdir_events.next().fuse() => {
378                if let Some(DirEvent::NewConsensus) = event {
379                    let netdir = netdir_provider.netdir(Timeliness::Timely)?;
380                    mgr.inner.write().expect("poisoned lock")
381                        .update_vanguard_sets(&mgr.runtime, &mgr.storage, &netdir)?;
382                }
383
384                Ok(ShutdownStatus::Continue)
385            },
386            _config = config_rx.recv().fuse() => {
387                if let Some(netdir) = Self::timely_netdir(&netdir_provider)? {
388                    // If we have a NetDir, replenish the vanguard sets that don't have enough vanguards.
389                    //
390                    // For example, if the config change enables full vanguards for the first time,
391                    // this will cause the L3 vanguard set to be populated.
392                    mgr.inner.write().expect("poisoned lock")
393                        .update_vanguard_sets(&mgr.runtime, &mgr.storage, &netdir)?;
394                }
395
396                Ok(ShutdownStatus::Continue)
397            },
398            () = sleep_fut.fuse() => {
399                // A vanguard expired, time to run the cleanup
400                Ok(ShutdownStatus::Continue)
401            },
402        }
403    }
404
405    /// Return a timely `NetDir`, if one is available.
406    ///
407    /// Returns `None` if no directory information is available.
408    fn timely_netdir(
409        netdir_provider: &Arc<dyn NetDirProvider>,
410    ) -> Result<Option<Arc<NetDir>>, VanguardMgrError> {
411        use tor_netdir::Error as NetDirError;
412
413        match netdir_provider.netdir(Timeliness::Timely) {
414            Ok(netdir) => Ok(Some(netdir)),
415            Err(NetDirError::NoInfo) | Err(NetDirError::NotEnoughInfo) => Ok(None),
416            Err(e) => Err(e.into()),
417        }
418    }
419
420    /// Rotate the vanguards that have expired,
421    /// returning how long until the next vanguard will expire,
422    /// or `None` if there are no vanguards in any of our sets.
423    fn rotate_expired(
424        &self,
425        netdir_provider: &Arc<dyn NetDirProvider>,
426        now: SystemTime,
427    ) -> Result<Option<Duration>, VanguardMgrError> {
428        let mut inner = self.inner.write().expect("poisoned lock");
429        let inner = &mut *inner;
430
431        let vanguard_sets = &mut inner.vanguard_sets;
432        let expired_count = vanguard_sets.remove_expired(now);
433
434        if expired_count > 0 {
435            info!("Rotating vanguards");
436        }
437
438        if let Some(netdir) = Self::timely_netdir(netdir_provider)? {
439            // If we have a NetDir, replenish the vanguard sets that don't have enough vanguards.
440            inner.update_vanguard_sets(&self.runtime, &self.storage, &netdir)?;
441        }
442
443        let Some(expiry) = inner.vanguard_sets.next_expiry() else {
444            // Both vanguard sets are empty
445            return Ok(None);
446        };
447
448        expiry
449            .duration_since(now)
450            .map_err(|_| internal!("when > now, but now is later than when?!").into())
451            .map(Some)
452    }
453
454    /// Get the current [`VanguardMode`].
455    pub fn mode(&self) -> VanguardMode {
456        self.inner.read().expect("poisoned lock").mode
457    }
458}
459
460impl Inner {
461    /// Update the vanguard sets, handling any potential vanguard parameter changes.
462    ///
463    /// This updates the [`VanguardSets`]s based on the [`VanguardParams`]
464    /// derived from the new `NetDir`, replenishing the sets if necessary.
465    ///
466    /// NOTE: if the new `VanguardParams` specify different lifetime ranges
467    /// than the previous `VanguardParams`, the new lifetime requirements only
468    /// apply to newly selected vanguards. They are **not** retroactively applied
469    /// to our existing vanguards.
470    //
471    // TODO(#1352): we might want to revisit this decision.
472    // We could, for example, adjust the lifetime of our existing vanguards
473    // to comply with the new lifetime requirements.
474    fn update_vanguard_sets<R: Runtime>(
475        &mut self,
476        runtime: &R,
477        storage: &DynStorageHandle<VanguardSets>,
478        netdir: &Arc<NetDir>,
479    ) -> Result<(), VanguardMgrError> {
480        let params = VanguardParams::try_from(netdir.params())
481            .map_err(into_internal!("invalid NetParameters"))?;
482
483        // Update our params with the new values.
484        self.update_params(params.clone());
485
486        self.vanguard_sets.remove_unlisted(netdir);
487
488        // If we loaded some vanguards from persistent storage but we still need more,
489        // we select them here.
490        //
491        // If full vanguards are not enabled and we started with an empty (default)
492        // vanguard set, we populate the sets here.
493        //
494        // If we have already populated the vanguard sets in a previous iteration,
495        // this will ensure they have enough vanguards.
496        self.vanguard_sets
497            .replenish_vanguards(runtime, netdir, &params, self.mode)?;
498
499        // Flush the vanguard sets to disk.
500        self.flush_to_storage(storage)?;
501
502        Ok(())
503    }
504
505    /// Update our vanguard params.
506    fn update_params(&mut self, new_params: VanguardParams) {
507        self.params = new_params;
508    }
509
510    /// Flush the vanguard sets to storage, if the mode is "vanguards-full".
511    fn flush_to_storage(
512        &self,
513        storage: &DynStorageHandle<VanguardSets>,
514    ) -> Result<(), VanguardMgrError> {
515        match self.mode {
516            VanguardMode::Lite | VanguardMode::Disabled => Ok(()),
517            VanguardMode::Full => {
518                debug!("The vanguards may have changed; flushing to vanguard state file");
519                Ok(storage.store(&self.vanguard_sets)?)
520            }
521        }
522    }
523}
524
525#[cfg(any(test, feature = "testing"))]
526use {
527    tor_config::ExplicitOrAuto, tor_netdir::testprovider::TestNetDirProvider,
528    tor_persist::TestingStateMgr, tor_rtmock::MockRuntime,
529};
530
531/// Helpers for tests involving vanguards
532#[cfg(any(test, feature = "testing"))]
533impl VanguardMgr<MockRuntime> {
534    /// Create a new VanguardMgr for testing.
535    pub fn new_testing(
536        rt: &MockRuntime,
537        mode: VanguardMode,
538    ) -> Result<Arc<VanguardMgr<MockRuntime>>, VanguardMgrError> {
539        let config = VanguardConfig {
540            mode: ExplicitOrAuto::Explicit(mode),
541        };
542        let statemgr = TestingStateMgr::new();
543        let lock = statemgr.try_lock()?;
544        assert!(lock.held());
545        // TODO(#1382): has_onion_svc doesn't matter right now
546        let has_onion_svc = false;
547        Ok(Arc::new(VanguardMgr::new(
548            &config,
549            rt.clone(),
550            statemgr,
551            has_onion_svc,
552        )?))
553    }
554
555    /// Wait until the vanguardmgr has populated its vanguard sets.
556    ///
557    /// Returns a [`TestNetDirProvider`] that can be used to notify
558    /// the `VanguardMgr` of netdir changes.
559    pub async fn init_vanguard_sets(
560        self: &Arc<VanguardMgr<MockRuntime>>,
561        netdir: &NetDir,
562    ) -> Result<Arc<TestNetDirProvider>, VanguardMgrError> {
563        let netdir_provider = Arc::new(TestNetDirProvider::new());
564        self.launch_background_tasks(&(netdir_provider.clone() as Arc<dyn NetDirProvider>))?;
565        self.runtime.progress_until_stalled().await;
566
567        // Call set_netdir_and_notify to trigger an event
568        netdir_provider
569            .set_netdir_and_notify(Arc::new(netdir.clone()))
570            .await;
571
572        // Wait until the vanguard mgr has finished handling the netdir event.
573        self.runtime.progress_until_stalled().await;
574
575        Ok(netdir_provider)
576    }
577}
578
579/// The vanguard layer.
580#[derive(Debug, Clone, Copy, PartialEq)] //
581#[derive(derive_more::Display)] //
582#[non_exhaustive]
583pub enum Layer {
584    /// L2 vanguard.
585    #[display("layer 2")]
586    Layer2,
587    /// L3 vanguard.
588    #[display("layer 3")]
589    Layer3,
590}
591
592#[cfg(test)]
593mod test {
594    // @@ begin test lint list maintained by maint/add_warning @@
595    #![allow(clippy::bool_assert_comparison)]
596    #![allow(clippy::clone_on_copy)]
597    #![allow(clippy::dbg_macro)]
598    #![allow(clippy::mixed_attributes_style)]
599    #![allow(clippy::print_stderr)]
600    #![allow(clippy::print_stdout)]
601    #![allow(clippy::single_char_pattern)]
602    #![allow(clippy::unwrap_used)]
603    #![allow(clippy::unchecked_time_subtraction)]
604    #![allow(clippy::useless_vec)]
605    #![allow(clippy::needless_pass_by_value)]
606    #![allow(clippy::string_slice)] // See arti#2571
607    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
608
609    use std::{fmt, time};
610
611    use set::TimeBoundVanguard;
612    use tor_config::ExplicitOrAuto;
613    use tor_relay_selection::RelayExclusion;
614
615    use super::*;
616
617    use Layer::*;
618    use tor_basic_utils::test_rng::testing_rng;
619    use tor_linkspec::{HasRelayIds, RelayIds};
620    use tor_netdir::{
621        testnet::{self, construct_custom_netdir_with_params},
622        testprovider::TestNetDirProvider,
623    };
624    use tor_persist::FsStateMgr;
625    use tor_rtmock::MockRuntime;
626
627    use itertools::Itertools;
628
629    /// Enable lite vanguards for onion services.
630    const ENABLE_LITE_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 1)];
631
632    /// Enable full vanguards for hidden services.
633    const ENABLE_FULL_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 2)];
634
635    /// A valid vanguard state file.
636    const VANGUARDS_JSON: &str = include_str!("../testdata/vanguards.json");
637
638    /// A invalid vanguard state file.
639    const INVALID_VANGUARDS_JSON: &str = include_str!("../testdata/vanguards_invalid.json");
640
641    /// Create the `StateMgr`, populating the vanguards.json state file with the specified JSON string.
642    fn state_dir_with_vanguards(vanguards_json: &str) -> (FsStateMgr, tempfile::TempDir) {
643        let dir = tempfile::TempDir::new().unwrap();
644        std::fs::create_dir_all(dir.path().join("state")).unwrap();
645        std::fs::write(dir.path().join("state/vanguards.json"), vanguards_json).unwrap();
646
647        let statemgr = FsStateMgr::from_path_and_mistrust(
648            dir.path(),
649            &fs_mistrust::Mistrust::new_dangerously_trust_everyone(),
650        )
651        .unwrap();
652
653        (statemgr, dir)
654    }
655
656    impl fmt::Debug for Vanguard<'_> {
657        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
658            f.debug_struct("Vanguard").finish()
659        }
660    }
661
662    impl Inner {
663        /// Return the L2 vanguard set.
664        pub(super) fn l2_vanguards(&self) -> &Vec<TimeBoundVanguard> {
665            self.vanguard_sets.l2_vanguards()
666        }
667
668        /// Return the L3 vanguard set.
669        pub(super) fn l3_vanguards(&self) -> &Vec<TimeBoundVanguard> {
670            self.vanguard_sets.l3_vanguards()
671        }
672    }
673
674    /// Return a maximally permissive RelaySelector for a vanguard.
675    fn permissive_selector() -> RelaySelector<'static> {
676        RelaySelector::new(
677            tor_relay_selection::RelayUsage::vanguard(),
678            RelayExclusion::no_relays_excluded(),
679        )
680    }
681
682    /// Look up the vanguard in the specified VanguardSet.
683    fn find_in_set<R: Runtime>(
684        relay_ids: &RelayIds,
685        mgr: &VanguardMgr<R>,
686        layer: Layer,
687    ) -> Option<TimeBoundVanguard> {
688        let inner = mgr.inner.read().unwrap();
689
690        let vanguards = match layer {
691            Layer2 => inner.l2_vanguards(),
692            Layer3 => inner.l3_vanguards(),
693        };
694
695        // Look up the TimeBoundVanguard that corresponds to this Vanguard,
696        // and figure out its expiry.
697        vanguards.iter().find(|v| v.id == *relay_ids).cloned()
698    }
699
700    /// Get the total number of vanguard entries (L2 + L3).
701    fn vanguard_count<R: Runtime>(mgr: &VanguardMgr<R>) -> usize {
702        let inner = mgr.inner.read().unwrap();
703        inner.l2_vanguards().len() + inner.l3_vanguards().len()
704    }
705
706    /// Return a `Duration` representing how long until this vanguard expires.
707    fn duration_until_expiry<R: Runtime>(
708        relay_ids: &RelayIds,
709        mgr: &VanguardMgr<R>,
710        runtime: &R,
711        layer: Layer,
712    ) -> Duration {
713        // Look up the TimeBoundVanguard that corresponds to this Vanguard,
714        // and figure out its expiry.
715        let vanguard = find_in_set(relay_ids, mgr, layer).unwrap();
716
717        vanguard
718            .when
719            .duration_since(runtime.wallclock())
720            .unwrap_or_default()
721    }
722
723    /// Assert the lifetime of the specified `vanguard` is within the bounds of its `layer`.
724    fn assert_expiry_in_bounds<R: Runtime>(
725        vanguard: &Vanguard<'_>,
726        mgr: &VanguardMgr<R>,
727        runtime: &R,
728        params: &VanguardParams,
729        layer: Layer,
730    ) {
731        let (min, max) = match layer {
732            Layer2 => (params.l2_lifetime_min(), params.l2_lifetime_max()),
733            Layer3 => (params.l3_lifetime_min(), params.l3_lifetime_max()),
734        };
735
736        let vanguard = RelayIds::from_relay_ids(vanguard.relay());
737        // This is not exactly the lifetime of the vanguard,
738        // but rather the time left until it expires (but it's close enough for our purposes).
739        let lifetime = duration_until_expiry(&vanguard, mgr, runtime, layer);
740
741        assert!(
742            lifetime >= min && lifetime <= max,
743            "lifetime {lifetime:?} not between {min:?} and {max:?}",
744        );
745    }
746
747    /// Assert that the vanguard manager's pools are empty.
748    fn assert_sets_empty<R: Runtime>(vanguardmgr: &VanguardMgr<R>) {
749        let inner = vanguardmgr.inner.read().unwrap();
750        // The sets are initially empty, and the targets are set to 0
751        assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
752        assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
753        assert_eq!(vanguard_count(vanguardmgr), 0);
754    }
755
756    /// Assert that the vanguard manager's pools have been filled.
757    fn assert_sets_filled<R: Runtime>(vanguardmgr: &VanguardMgr<R>, params: &VanguardParams) {
758        let inner = vanguardmgr.inner.read().unwrap();
759        let l2_pool_size = params.l2_pool_size();
760        // The sets are initially empty
761        assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
762
763        if inner.mode == VanguardMode::Full {
764            assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
765            let l3_pool_size = params.l3_pool_size();
766            assert_eq!(vanguard_count(vanguardmgr), l2_pool_size + l3_pool_size);
767        }
768    }
769
770    /// Assert the target size of the specified vanguard set matches the target from `params`.
771    fn assert_set_vanguards_targets_match_params<R: Runtime>(
772        mgr: &VanguardMgr<R>,
773        params: &VanguardParams,
774    ) {
775        let inner = mgr.inner.read().unwrap();
776        assert_eq!(
777            inner.vanguard_sets.l2_vanguards_target(),
778            params.l2_pool_size()
779        );
780        if inner.mode == VanguardMode::Full {
781            assert_eq!(
782                inner.vanguard_sets.l3_vanguards_target(),
783                params.l3_pool_size()
784            );
785        }
786    }
787
788    #[test]
789    fn full_vanguards_disabled() {
790        MockRuntime::test_with_various(|rt| async move {
791            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
792            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
793            let mut rng = testing_rng();
794            // Wait until the vanguard manager has bootstrapped
795            // (otherwise we'll get a BootstrapRequired error)
796            let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
797
798            // Cannot select an L3 vanguard when running in "Lite" mode.
799            let err = vanguardmgr
800                .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
801                .unwrap_err();
802            assert!(
803                matches!(
804                    err,
805                    VanguardMgrError::LayerNotSupported {
806                        layer: Layer::Layer3,
807                        mode: VanguardMode::Lite
808                    }
809                ),
810                "{err}"
811            );
812        });
813    }
814
815    #[test]
816    fn background_task_not_spawned() {
817        MockRuntime::test_with_various(|rt| async move {
818            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
819            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
820            let mut rng = testing_rng();
821
822            // The sets are initially empty
823            assert_sets_empty(&vanguardmgr);
824
825            // VanguardMgr::launch_background tasks was not called, so select_vanguard will return
826            // an error (because the vanguard sets are empty)
827            let err = vanguardmgr
828                .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
829                .unwrap_err();
830
831            assert!(
832                matches!(
833                    err,
834                    VanguardMgrError::BootstrapRequired {
835                        action: "select vanguard"
836                    }
837                ),
838                "{err:?}"
839            );
840        });
841    }
842
843    #[test]
844    fn select_vanguards() {
845        MockRuntime::test_with_various(|rt| async move {
846            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Full).unwrap();
847
848            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
849            let params = VanguardParams::try_from(netdir.params()).unwrap();
850            let mut rng = testing_rng();
851
852            // The sets are initially empty
853            assert_sets_empty(&vanguardmgr);
854
855            // Wait until the vanguard manager has bootstrapped
856            let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
857
858            assert_sets_filled(&vanguardmgr, &params);
859
860            let vanguard1 = vanguardmgr
861                .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
862                .unwrap();
863            assert_expiry_in_bounds(&vanguard1, &vanguardmgr, &rt, &params, Layer2);
864
865            let exclusion = RelayExclusion::exclude_identities(
866                vanguard1
867                    .relay()
868                    .identities()
869                    .map(|id| id.to_owned())
870                    .collect(),
871            );
872            let selector =
873                RelaySelector::new(tor_relay_selection::RelayUsage::vanguard(), exclusion);
874
875            let vanguard2 = vanguardmgr
876                .select_vanguard(&mut rng, &netdir, Layer3, &selector)
877                .unwrap();
878
879            assert_expiry_in_bounds(&vanguard2, &vanguardmgr, &rt, &params, Layer3);
880            // Ensure we didn't select the same vanguard twice
881            assert_ne!(
882                vanguard1.relay().identities().collect_vec(),
883                vanguard2.relay().identities().collect_vec()
884            );
885        });
886    }
887
888    /// Override the vanguard params from the netdir, returning the new VanguardParams.
889    ///
890    /// This also waits until the vanguard manager has had a chance to process the changes.
891    async fn install_new_params(
892        rt: &MockRuntime,
893        netdir_provider: &TestNetDirProvider,
894        params: impl IntoIterator<Item = (&str, i32)>,
895    ) -> VanguardParams {
896        let new_netdir = testnet::construct_custom_netdir_with_params(|_, _, _| {}, params, None)
897            .unwrap()
898            .unwrap_if_sufficient()
899            .unwrap();
900        let new_params = VanguardParams::try_from(new_netdir.params()).unwrap();
901
902        netdir_provider.set_netdir_and_notify(new_netdir).await;
903
904        // Wait until the vanguard mgr has finished handling the new netdir.
905        rt.progress_until_stalled().await;
906
907        new_params
908    }
909
910    /// Switch the vanguard "mode" of the VanguardMgr to `mode`,
911    /// by setting the vanguards-hs-service parameter.
912    //
913    // TODO(#1382): use this instead of switch_hs_mode_config.
914    #[allow(unused)]
915    async fn switch_hs_mode(
916        rt: &MockRuntime,
917        vanguardmgr: &VanguardMgr<MockRuntime>,
918        netdir_provider: &TestNetDirProvider,
919        mode: VanguardMode,
920    ) {
921        use VanguardMode::*;
922
923        let _params = match mode {
924            Lite => install_new_params(rt, netdir_provider, ENABLE_LITE_VANGUARDS).await,
925            Full => install_new_params(rt, netdir_provider, ENABLE_FULL_VANGUARDS).await,
926            Disabled => panic!("cannot disable vanguards in the vanguard tests!"),
927        };
928
929        assert_eq!(vanguardmgr.mode(), mode);
930    }
931
932    /// Switch the vanguard "mode" of the VanguardMgr to `mode`,
933    /// by calling `VanguardMgr::reconfigure`.
934    fn switch_hs_mode_config(vanguardmgr: &VanguardMgr<MockRuntime>, mode: VanguardMode) {
935        let _ = vanguardmgr
936            .reconfigure(&VanguardConfig {
937                mode: ExplicitOrAuto::Explicit(mode),
938            })
939            .unwrap();
940
941        assert_eq!(vanguardmgr.mode(), mode);
942    }
943
944    /// Use a new NetDir that excludes one of our L2 vanguards
945    async fn install_netdir_excluding_vanguard<'a>(
946        runtime: &MockRuntime,
947        vanguard: &Vanguard<'_>,
948        params: impl IntoIterator<Item = (&'a str, i32)>,
949        netdir_provider: &TestNetDirProvider,
950    ) -> NetDir {
951        let new_netdir = construct_custom_netdir_with_params(
952            |_idx, bld, _| {
953                let md_so_far = bld.md.testing_md().unwrap();
954                if md_so_far.ed25519_id() == vanguard.relay().id() {
955                    bld.omit_rs = true;
956                }
957            },
958            params,
959            None,
960        )
961        .unwrap()
962        .unwrap_if_sufficient()
963        .unwrap();
964
965        netdir_provider
966            .set_netdir_and_notify(new_netdir.clone())
967            .await;
968        // Wait until the vanguard mgr has finished handling the new netdir.
969        runtime.progress_until_stalled().await;
970
971        new_netdir
972    }
973
974    #[test]
975    fn override_vanguard_set_size() {
976        MockRuntime::test_with_various(|rt| async move {
977            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
978            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
979            // Wait until the vanguard manager has bootstrapped
980            let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
981
982            let params = VanguardParams::try_from(netdir.params()).unwrap();
983            let old_size = params.l2_pool_size();
984            assert_set_vanguards_targets_match_params(&vanguardmgr, &params);
985
986            const PARAMS: [[(&str, i32); 2]; 2] = [
987                [("guard-hs-l2-number", 1), ("guard-hs-l3-number", 10)],
988                [("guard-hs-l2-number", 10), ("guard-hs-l3-number", 10)],
989            ];
990
991            for params in PARAMS {
992                let new_params = install_new_params(&rt, &netdir_provider, params).await;
993
994                // Ensure the target size was updated.
995                assert_set_vanguards_targets_match_params(&vanguardmgr, &new_params);
996                {
997                    let inner = vanguardmgr.inner.read().unwrap();
998                    let l2_vanguards = inner.l2_vanguards();
999                    let l3_vanguards = inner.l3_vanguards();
1000                    let new_l2_size = params[0].1 as usize;
1001                    if new_l2_size < old_size {
1002                        // The actual size of the set hasn't changed: it's OK to have more vanguards than
1003                        // needed in the set (they extraneous ones will eventually expire).
1004                        assert_eq!(l2_vanguards.len(), old_size);
1005                    } else {
1006                        // The new size is greater, so we have more L2 vanguards now.
1007                        assert_eq!(l2_vanguards.len(), new_l2_size);
1008                    }
1009                    // There are no L3 vanguards because full vanguards are not in use.
1010                    assert_eq!(l3_vanguards.len(), 0);
1011                }
1012            }
1013        });
1014    }
1015
1016    #[test]
1017    fn expire_vanguards() {
1018        MockRuntime::test_with_various(|rt| async move {
1019            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
1020            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
1021            let params = VanguardParams::try_from(netdir.params()).unwrap();
1022            let initial_l2_number = params.l2_pool_size();
1023
1024            // Wait until the vanguard manager has bootstrapped
1025            let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
1026            assert_eq!(vanguard_count(&vanguardmgr), params.l2_pool_size());
1027
1028            // Find the RelayIds of the vanguard that is due to expire next
1029            let vanguard_id = {
1030                let inner = vanguardmgr.inner.read().unwrap();
1031                let next_expiry = inner.vanguard_sets.next_expiry().unwrap();
1032                inner
1033                    .l2_vanguards()
1034                    .iter()
1035                    .find(|v| v.when == next_expiry)
1036                    .cloned()
1037                    .unwrap()
1038                    .id
1039            };
1040
1041            const FEWER_VANGUARDS_PARAM: [(&str, i32); 1] = [("guard-hs-l2-number", 1)];
1042            // Set the number of L2 vanguards to a lower value to ensure the vanguard that is about
1043            // to expire is not replaced. This allows us to test that it has indeed expired
1044            // (we can't simply check that the relay is no longer is the set,
1045            // because it's possible for the set to get replenished with the same relay).
1046            let new_params = install_new_params(&rt, &netdir_provider, FEWER_VANGUARDS_PARAM).await;
1047
1048            // The vanguard has not expired yet.
1049            let timebound_vanguard = find_in_set(&vanguard_id, &vanguardmgr, Layer2);
1050            assert!(timebound_vanguard.is_some());
1051            assert_eq!(vanguard_count(&vanguardmgr), initial_l2_number);
1052
1053            let lifetime = duration_until_expiry(&vanguard_id, &vanguardmgr, &rt, Layer2);
1054            // Wait until this vanguard expires
1055            rt.advance_by(lifetime).await.unwrap();
1056            rt.progress_until_stalled().await;
1057
1058            let timebound_vanguard = find_in_set(&vanguard_id, &vanguardmgr, Layer2);
1059
1060            // The vanguard expired, but was not replaced.
1061            assert!(timebound_vanguard.is_none());
1062            assert_eq!(vanguard_count(&vanguardmgr), initial_l2_number - 1);
1063
1064            // Wait until more vanguards expire. This will reduce the set size to 1
1065            // (the new target size we set by overriding the params).
1066            for _ in 0..initial_l2_number - 1 {
1067                let vanguard_id = {
1068                    let inner = vanguardmgr.inner.read().unwrap();
1069                    let next_expiry = inner.vanguard_sets.next_expiry().unwrap();
1070                    inner
1071                        .l2_vanguards()
1072                        .iter()
1073                        .find(|v| v.when == next_expiry)
1074                        .cloned()
1075                        .unwrap()
1076                        .id
1077                };
1078                let lifetime = duration_until_expiry(&vanguard_id, &vanguardmgr, &rt, Layer2);
1079                rt.advance_by(lifetime).await.unwrap();
1080
1081                rt.progress_until_stalled().await;
1082            }
1083
1084            assert_eq!(vanguard_count(&vanguardmgr), new_params.l2_pool_size());
1085
1086            // Update the L2 set size again, to force the vanguard manager to replenish the L2 set.
1087            const MORE_VANGUARDS_PARAM: [(&str, i32); 1] = [("guard-hs-l2-number", 5)];
1088            // Set the number of L2 vanguards to a lower value to ensure the vanguard that is about
1089            // to expire is not replaced. This allows us to test that it has indeed expired
1090            // (we can't simply check that the relay is no longer is the set,
1091            // because it's possible for the set to get replenished with the same relay).
1092            let new_params = install_new_params(&rt, &netdir_provider, MORE_VANGUARDS_PARAM).await;
1093
1094            // Check that we replaced the expired vanguard with a new one:
1095            assert_eq!(vanguard_count(&vanguardmgr), new_params.l2_pool_size());
1096
1097            {
1098                let inner = vanguardmgr.inner.read().unwrap();
1099                let l2_count = inner.l2_vanguards().len();
1100                assert_eq!(l2_count, new_params.l2_pool_size());
1101            }
1102        });
1103    }
1104
1105    #[test]
1106    fn full_vanguards_persistence() {
1107        MockRuntime::test_with_various(|rt| async move {
1108            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
1109
1110            let netdir =
1111                construct_custom_netdir_with_params(|_, _, _| {}, ENABLE_LITE_VANGUARDS, None)
1112                    .unwrap()
1113                    .unwrap_if_sufficient()
1114                    .unwrap();
1115            let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
1116
1117            // Full vanguards are not enabled, so we don't expect anything to be written
1118            // to persistent storage.
1119            assert_eq!(vanguardmgr.mode(), VanguardMode::Lite);
1120            assert!(vanguardmgr.storage.load().unwrap().is_none());
1121
1122            let mut rng = testing_rng();
1123            assert!(
1124                vanguardmgr
1125                    .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
1126                    .is_err()
1127            );
1128
1129            // Enable full vanguards again.
1130            //
1131            // We expect VanguardMgr to populate the L3 set, and write the VanguardSets to storage.
1132            switch_hs_mode_config(&vanguardmgr, VanguardMode::Full);
1133            rt.progress_until_stalled().await;
1134
1135            let vanguard_sets_orig = vanguardmgr.storage.load().unwrap();
1136            assert!(
1137                vanguardmgr
1138                    .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
1139                    .is_ok()
1140            );
1141
1142            // Switch to lite vanguards.
1143            switch_hs_mode_config(&vanguardmgr, VanguardMode::Lite);
1144
1145            // The vanguard sets should not change when switching between lite and full vanguards.
1146            assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
1147            switch_hs_mode_config(&vanguardmgr, VanguardMode::Full);
1148            assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
1149
1150            // TODO HS-VANGUARDS: we may want to disable the ability to switch back to lite
1151            // vanguards.
1152
1153            // Switch to lite vanguards and remove a relay from the consensus.
1154            // The relay should *not* be persisted to storage until we switch back to full
1155            // vanguards.
1156            switch_hs_mode_config(&vanguardmgr, VanguardMode::Lite);
1157
1158            let mut rng = testing_rng();
1159            let excluded_vanguard = vanguardmgr
1160                .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
1161                .unwrap();
1162
1163            let _ = install_netdir_excluding_vanguard(
1164                &rt,
1165                &excluded_vanguard,
1166                ENABLE_LITE_VANGUARDS,
1167                &netdir_provider,
1168            )
1169            .await;
1170
1171            // The vanguard sets from storage haven't changed, because we are in "lite" mode.
1172            assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
1173            let _ = install_netdir_excluding_vanguard(
1174                &rt,
1175                &excluded_vanguard,
1176                ENABLE_FULL_VANGUARDS,
1177                &netdir_provider,
1178            )
1179            .await;
1180        });
1181    }
1182
1183    #[test]
1184    fn load_from_state_file() {
1185        MockRuntime::test_with_various(|rt| async move {
1186            // Set the wallclock to a time when some of the stored vanguards are still valid.
1187            let now = time::UNIX_EPOCH + Duration::from_secs(1610000000);
1188            rt.jump_wallclock(now);
1189
1190            let config = VanguardConfig {
1191                mode: ExplicitOrAuto::Explicit(VanguardMode::Full),
1192            };
1193
1194            // The state file contains no vanguards
1195            let (statemgr, _dir) =
1196                state_dir_with_vanguards(r#"{ "l2_vanguards": [], "l3_vanguards": [] }"#);
1197            let vanguardmgr = VanguardMgr::new(&config, rt.clone(), statemgr, false).unwrap();
1198            {
1199                let inner = vanguardmgr.inner.read().unwrap();
1200
1201                // The vanguard sets should be empty too
1202                assert!(inner.vanguard_sets.l2().is_empty());
1203                assert!(inner.vanguard_sets.l3().is_empty());
1204            }
1205
1206            let (statemgr, _dir) = state_dir_with_vanguards(VANGUARDS_JSON);
1207            let vanguardmgr =
1208                Arc::new(VanguardMgr::new(&config, rt.clone(), statemgr, false).unwrap());
1209            let (initial_l2, initial_l3) = {
1210                let inner = vanguardmgr.inner.read().unwrap();
1211                let l2_vanguards = inner.vanguard_sets.l2_vanguards().clone();
1212                let l3_vanguards = inner.vanguard_sets.l3_vanguards().clone();
1213
1214                // The sets actually contain 4 and 5 vanguards, respectively,
1215                // but the expired ones are discarded.
1216                assert_eq!(l2_vanguards.len(), 3);
1217                assert_eq!(l3_vanguards.len(), 2);
1218                // We don't know how many vanguards we're going to need
1219                // until we fetch the consensus.
1220                assert_eq!(inner.vanguard_sets.l2_vanguards_target(), 0);
1221                assert_eq!(inner.vanguard_sets.l3_vanguards_target(), 0);
1222                assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
1223                assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
1224
1225                (l2_vanguards, l3_vanguards)
1226            };
1227
1228            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
1229            let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
1230            {
1231                let inner = vanguardmgr.inner.read().unwrap();
1232                let l2_vanguards = inner.vanguard_sets.l2_vanguards();
1233                let l3_vanguards = inner.vanguard_sets.l3_vanguards();
1234
1235                // The sets were replenished with more vanguards
1236                assert_eq!(l2_vanguards.len(), 4);
1237                assert_eq!(l3_vanguards.len(), 8);
1238                // We now know we need 4 L2 vanguards and 8 L3 ones.
1239                assert_eq!(inner.vanguard_sets.l2_vanguards_target(), 4);
1240                assert_eq!(inner.vanguard_sets.l3_vanguards_target(), 8);
1241                assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
1242                assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
1243
1244                // All of the vanguards read from the state file should still be in the sets.
1245                assert!(initial_l2.iter().all(|v| l2_vanguards.contains(v)));
1246                assert!(initial_l3.iter().all(|v| l3_vanguards.contains(v)));
1247            }
1248        });
1249    }
1250
1251    #[test]
1252    fn invalid_state_file() {
1253        MockRuntime::test_with_various(|rt| async move {
1254            let config = VanguardConfig {
1255                mode: ExplicitOrAuto::Explicit(VanguardMode::Full),
1256            };
1257            let (statemgr, _dir) = state_dir_with_vanguards(INVALID_VANGUARDS_JSON);
1258            let res = VanguardMgr::new(&config, rt.clone(), statemgr, false);
1259            assert!(matches!(res, Err(VanguardMgrError::State(_))));
1260        });
1261    }
1262}