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

            
7
pub mod config;
8
mod err;
9
mod set;
10

            
11
use std::sync::{Arc, RwLock, Weak};
12
use std::time::{Duration, SystemTime};
13

            
14
use futures::stream::BoxStream;
15
use futures::task::SpawnExt as _;
16
use futures::{future, FutureExt as _};
17
use futures::{select_biased, StreamExt as _};
18
use postage::stream::Stream as _;
19
use postage::watch;
20
use rand::RngCore;
21

            
22
use tor_async_utils::PostageWatchSenderExt as _;
23
use tor_config::ReconfigureError;
24
use tor_error::{error_report, internal, into_internal};
25
use tor_netdir::{DirEvent, NetDir, NetDirProvider, Timeliness};
26
use tor_persist::{DynStorageHandle, StateMgr};
27
use tor_relay_selection::RelaySelector;
28
use tor_rtcompat::Runtime;
29
use tracing::{debug, info};
30

            
31
use crate::{RetireCircuits, VanguardMode};
32

            
33
use set::VanguardSets;
34

            
35
use crate::VanguardConfig;
36
pub use config::VanguardParams;
37
pub use err::VanguardMgrError;
38
pub use set::Vanguard;
39

            
40
/// The key used for storing the vanguard sets to persistent storage using `StateMgr`.
41
const STORAGE_KEY: &str = "vanguards";
42

            
43
/// The vanguard manager.
44
pub struct VanguardMgr<R: Runtime> {
45
    /// The mutable state.
46
    inner: RwLock<Inner>,
47
    /// The runtime.
48
    runtime: R,
49
    /// The persistent storage handle, used for writing the vanguard sets to disk
50
    /// if full vanguards are enabled.
51
    storage: DynStorageHandle<VanguardSets>,
52
}
53

            
54
/// The mutable inner state of [`VanguardMgr`].
55
struct Inner {
56
    /// The current vanguard parameters.
57
    params: VanguardParams,
58
    /// Whether to use full, lite, or no vanguards.
59
    ///
60
    // TODO(#1382): we should derive the mode from the
61
    // vanguards-enabled and vanguards-hs-service consensus params.
62
    mode: VanguardMode,
63
    /// The L2 and L3 vanguards.
64
    ///
65
    /// The L3 vanguards are only used if we are running in
66
    /// [`Full`](VanguardMode::Full) vanguard mode.
67
    /// Otherwise, the L3 set is not populated, or read from.
68
    ///
69
    /// If [`Full`](VanguardMode::Full) vanguard mode is enabled,
70
    /// the vanguard sets will be persisted to disk whenever
71
    /// vanuards are rotated, added, or removed.
72
    ///
73
    /// The vanguard sets are updated and persisted to storage by
74
    /// [`update_vanguard_sets`](Inner::update_vanguard_sets).
75
    ///
76
    /// If the `VanguardSets` change while we are in "lite" mode,
77
    /// the changes will not *not* be written to storage.
78
    /// If we later switch to "full" vanguards, those previous changes still
79
    /// won't be persisted to storage: we only flush to storage if the
80
    /// [`VanguardSets`] change *while* we are in "full" mode
81
    /// (changing the [`VanguardMode`] does not constitute a change in the `VanguardSets`).
82
    //
83
    // TODO HS-VANGUARDS: the correct behaviour here might be to never switch back to lite mode
84
    // after enabling full vanguards. If we do that, persisting the vanguard sets will be simpler,
85
    // as we can just unconditionally flush to storage if the vanguard mode is switched to full.
86
    // Right now we can't do that, because we don't remember the "mode":
87
    // we derive it on the fly from `has_onion_svc` and the current `VanguardParams`.
88
    //
89
    ///
90
    /// This is initialized with the vanguard sets read from the vanguard state file,
91
    /// if the file exists, or with a [`Default`] `VanguardSets`, if it doesn't.
92
    ///
93
    /// Note: The `VanguardSets` are read from the vanguard state file
94
    /// even if full vanguards are not enabled. They are *not*, however, written
95
    /// to the state file unless full vanguards are in use.
96
    vanguard_sets: VanguardSets,
97
    /// Whether we're running an onion service.
98
    ///
99
    // TODO(#1382): This should be used for deciding whether to use the `vanguards_hs_service` or the
100
    // `vanguards_enabled` [`NetParameter`](tor_netdir::params::NetParameters).
101
    #[allow(unused)]
102
    has_onion_svc: bool,
103
    /// A channel for sending VanguardConfig changes to the vanguard maintenance task.
104
    config_tx: watch::Sender<VanguardConfig>,
105
}
106

            
107
/// Whether the [`VanguardMgr::maintain_vanguard_sets`] task
108
/// should continue running or shut down.
109
///
110
/// Returned from [`VanguardMgr::run_once`].
111
#[derive(Copy, Clone, Debug)]
112
enum ShutdownStatus {
113
    /// Continue calling `run_once`.
114
    Continue,
115
    /// The `VanguardMgr` was dropped, terminate the task.
116
    Terminate,
117
}
118

            
119
impl<R: Runtime> VanguardMgr<R> {
120
    /// Create a new `VanguardMgr`.
121
    ///
122
    /// The `state_mgr` handle is used for persisting the "vanguards-full" guard pools to disk.
123
1670
    pub fn new<S>(
124
1670
        config: &VanguardConfig,
125
1670
        runtime: R,
126
1670
        state_mgr: S,
127
1670
        has_onion_svc: bool,
128
1670
    ) -> Result<Self, VanguardMgrError>
129
1670
    where
130
1670
        S: StateMgr + Send + Sync + 'static,
131
1670
    {
132
1670
        // Note: we start out with default vanguard params, but we adjust them
133
1670
        // as soon as we obtain a NetDir (see Self::run_once()).
134
1670
        let params = VanguardParams::default();
135
1670
        let storage: DynStorageHandle<VanguardSets> = state_mgr.create_handle(STORAGE_KEY);
136

            
137
1670
        let vanguard_sets = match storage.load()? {
138
8
            Some(mut sets) => {
139
8
                info!("Loading vanguards from vanguard state file");
140
                // Discard the now-expired the vanguards
141
8
                let now = runtime.wallclock();
142
8
                let _ = sets.remove_expired(now);
143
8
                sets
144
            }
145
            None => {
146
1658
                debug!("Vanguard state file not found, selecting new vanguards");
147
                // Initially, all sets have a target size of 0.
148
                // This is OK because the target is only used for repopulating the vanguard sets,
149
                // and we can't repopulate the sets without a netdir.
150
                // The target gets adjusted once we obtain a netdir.
151
1658
                Default::default()
152
            }
153
        };
154

            
155
1666
        let (config_tx, _config_rx) = watch::channel();
156
1666
        let inner = Inner {
157
1666
            params,
158
1666
            mode: config.mode(),
159
1666
            vanguard_sets,
160
1666
            has_onion_svc,
161
1666
            config_tx,
162
1666
        };
163
1666

            
164
1666
        Ok(Self {
165
1666
            inner: RwLock::new(inner),
166
1666
            runtime,
167
1666
            storage,
168
1666
        })
169
1670
    }
170

            
171
    /// Launch the vanguard pool management tasks.
172
    ///
173
    /// These run until the `VanguardMgr` is dropped.
174
    //
175
    // This spawns [`VanguardMgr::maintain_vanguard_sets`].
176
92
    pub fn launch_background_tasks(
177
92
        self: &Arc<Self>,
178
92
        netdir_provider: &Arc<dyn NetDirProvider>,
179
92
    ) -> Result<(), VanguardMgrError>
180
92
    where
181
92
        R: Runtime,
182
92
    {
183
92
        let netdir_provider = Arc::clone(netdir_provider);
184
92
        let config_rx = self
185
92
            .inner
186
92
            .write()
187
92
            .expect("poisoned lock")
188
92
            .config_tx
189
92
            .subscribe();
190
92
        self.runtime
191
92
            .spawn(Self::maintain_vanguard_sets(
192
92
                Arc::downgrade(self),
193
92
                Arc::downgrade(&netdir_provider),
194
92
                config_rx,
195
92
            ))
196
92
            .map_err(|e| VanguardMgrError::Spawn(Arc::new(e)))?;
197

            
198
92
        Ok(())
199
92
    }
200

            
201
    /// Replace the configuration in this `VanguardMgr` with the specified `config`.
202
18
    pub fn reconfigure(&self, config: &VanguardConfig) -> Result<RetireCircuits, ReconfigureError> {
203
18
        // TODO(#1382): abolish VanguardConfig and derive the mode from the VanguardParams
204
18
        // and has_onion_svc instead.
205
18
        //
206
18
        // TODO(#1382): update has_onion_svc if the new config enables onion svc usage
207
18
        //
208
18
        // Perhaps we should always escalate to Full if we start running an onion service,
209
18
        // but not decessarily downgrade to lite if we stop.
210
18
        // See <https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/2083#note_3018173>
211
18
        let mut inner = self.inner.write().expect("poisoned lock");
212
18
        let new_mode = config.mode();
213
18
        if new_mode != inner.mode {
214
16
            inner.mode = new_mode;
215
16

            
216
16
            // Wake up the maintenance task to replenish the vanguard pools.
217
16
            inner.config_tx.maybe_send(|_| config.clone());
218
16

            
219
16
            Ok(RetireCircuits::All)
220
        } else {
221
2
            Ok(RetireCircuits::None)
222
        }
223
18
    }
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_exlusion` 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
116
    pub fn select_vanguard<'a, Rng: RngCore>(
264
116
        &self,
265
116
        rng: &mut Rng,
266
116
        netdir: &'a NetDir,
267
116
        layer: Layer,
268
116
        relay_selector: &RelaySelector<'a>,
269
116
    ) -> Result<Vanguard<'a>, VanguardMgrError> {
270
        use VanguardMode::*;
271

            
272
116
        let inner = self.inner.read().expect("poisoned lock");
273
116

            
274
116
        // All our vanguard sets are empty. This means select_vanguards() was called before
275
116
        // maintain_vanguard_sets() managed to obtain a netdir and populate the vanguard sets,
276
116
        // or all the vanguards have become unusable and we have been unable to replenish them.
277
116
        if inner.vanguard_sets.l2().is_empty() && inner.vanguard_sets.l3().is_empty() {
278
4
            return Err(VanguardMgrError::BootstrapRequired {
279
4
                action: "select vanguard",
280
4
            });
281
112
        }
282

            
283
104
        let relay =
284
112
            match (layer, inner.mode) {
285
64
                (Layer::Layer2, Full) | (Layer::Layer2, Lite) => inner
286
64
                    .vanguard_sets
287
64
                    .l2()
288
64
                    .pick_relay(rng, netdir, relay_selector),
289
                (Layer::Layer3, Full) => {
290
40
                    inner
291
40
                        .vanguard_sets
292
40
                        .l3()
293
40
                        .pick_relay(rng, netdir, relay_selector)
294
                }
295
                _ => {
296
8
                    return Err(VanguardMgrError::LayerNotSupported {
297
8
                        layer,
298
8
                        mode: inner.mode,
299
8
                    });
300
                }
301
            };
302

            
303
104
        relay.ok_or(VanguardMgrError::NoSuitableRelay(layer))
304
116
    }
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
92
    async fn maintain_vanguard_sets(
314
92
        mgr: Weak<Self>,
315
92
        netdir_provider: Weak<dyn NetDirProvider>,
316
92
        mut config_rx: watch::Receiver<VanguardConfig>,
317
92
    ) {
318
92
        let mut netdir_events = match netdir_provider.upgrade() {
319
88
            Some(provider) => provider.events(),
320
            None => {
321
4
                return;
322
            }
323
        };
324

            
325
        loop {
326
300
            match Self::run_once(
327
300
                Weak::clone(&mgr),
328
300
                Weak::clone(&netdir_provider),
329
300
                &mut netdir_events,
330
300
                &mut config_rx,
331
300
            )
332
300
            .await
333
            {
334
212
                Ok(ShutdownStatus::Continue) => continue,
335
                Ok(ShutdownStatus::Terminate) => {
336
                    debug!("Vanguard manager is shutting down");
337
                    break;
338
                }
339
4
                Err(e) => {
340
4
                    error_report!(e, "Vanguard manager crashed");
341
4
                    break;
342
                }
343
            }
344
        }
345
8
    }
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
300
    async fn run_once(
355
300
        mgr: Weak<Self>,
356
300
        netdir_provider: Weak<dyn NetDirProvider>,
357
300
        netdir_events: &mut BoxStream<'static, DirEvent>,
358
300
        config_rx: &mut watch::Receiver<VanguardConfig>,
359
300
    ) -> Result<ShutdownStatus, VanguardMgrError> {
360
300
        let (mgr, netdir_provider) = match (mgr.upgrade(), netdir_provider.upgrade()) {
361
300
            (Some(mgr), Some(netdir_provider)) => (mgr, netdir_provider),
362
            _ => return Ok(ShutdownStatus::Terminate),
363
        };
364

            
365
300
        let now = mgr.runtime.wallclock();
366
300
        let next_to_expire = mgr.rotate_expired(&netdir_provider, now)?;
367
        // A future that sleeps until the next vanguard expires
368
300
        let sleep_fut = async {
369
208
            if let Some(dur) = next_to_expire {
370
124
                let () = mgr.runtime.sleep(dur).await;
371
            } else {
372
84
                future::pending::<()>().await;
373
            }
374
16
        };
375

            
376
300
        select_biased! {
377
300
            event = netdir_events.next().fuse() => {
378
104
                if let Some(DirEvent::NewConsensus) = event {
379
104
                    let netdir = netdir_provider.netdir(Timeliness::Timely)?;
380
104
                    mgr.inner.write().expect("poisoned lock")
381
104
                        .update_vanguard_sets(&mgr.runtime, &mgr.storage, &netdir)?;
382
                }
383

            
384
100
                Ok(ShutdownStatus::Continue)
385
            },
386
300
            _config = config_rx.recv().fuse() => {
387
96
                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
8
                    mgr.inner.write().expect("poisoned lock")
393
8
                        .update_vanguard_sets(&mgr.runtime, &mgr.storage, &netdir)?;
394
88
                }
395

            
396
96
                Ok(ShutdownStatus::Continue)
397
            },
398
300
            () = sleep_fut.fuse() => {
399
                // A vanguard expired, time to run the cleanup
400
16
                Ok(ShutdownStatus::Continue)
401
            },
402
        }
403
216
    }
404

            
405
    /// Return a timely `NetDir`, if one is available.
406
    ///
407
    /// Returns `None` if no directory information is available.
408
396
    fn timely_netdir(
409
396
        netdir_provider: &Arc<dyn NetDirProvider>,
410
396
    ) -> Result<Option<Arc<NetDir>>, VanguardMgrError> {
411
        use tor_netdir::Error as NetDirError;
412

            
413
396
        match netdir_provider.netdir(Timeliness::Timely) {
414
132
            Ok(netdir) => Ok(Some(netdir)),
415
264
            Err(NetDirError::NoInfo) | Err(NetDirError::NotEnoughInfo) => Ok(None),
416
            Err(e) => Err(e.into()),
417
        }
418
396
    }
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
300
    fn rotate_expired(
424
300
        &self,
425
300
        netdir_provider: &Arc<dyn NetDirProvider>,
426
300
        now: SystemTime,
427
300
    ) -> Result<Option<Duration>, VanguardMgrError> {
428
300
        let mut inner = self.inner.write().expect("poisoned lock");
429
300
        let inner = &mut *inner;
430
300

            
431
300
        let vanguard_sets = &mut inner.vanguard_sets;
432
300
        let expired_count = vanguard_sets.remove_expired(now);
433
300

            
434
300
        if expired_count > 0 {
435
16
            info!("Rotating vanguards");
436
284
        }
437

            
438
300
        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
124
            inner.update_vanguard_sets(&self.runtime, &self.storage, &netdir)?;
441
176
        }
442

            
443
300
        let Some(expiry) = inner.vanguard_sets.next_expiry() else {
444
            // Both vanguard sets are empty
445
168
            return Ok(None);
446
        };
447

            
448
132
        expiry
449
132
            .duration_since(now)
450
132
            .map_err(|_| internal!("when > now, but now is later than when?!").into())
451
132
            .map(Some)
452
300
    }
453

            
454
    /// Get the current [`VanguardMode`].
455
144
    pub fn mode(&self) -> VanguardMode {
456
144
        self.inner.read().expect("poisoned lock").mode
457
144
    }
458
}
459

            
460
impl 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
236
    fn update_vanguard_sets<R: Runtime>(
475
236
        &mut self,
476
236
        runtime: &R,
477
236
        storage: &DynStorageHandle<VanguardSets>,
478
236
        netdir: &Arc<NetDir>,
479
236
    ) -> Result<(), VanguardMgrError> {
480
236
        let params = VanguardParams::try_from(netdir.params())
481
236
            .map_err(into_internal!("invalid NetParameters"))?;
482

            
483
        // Update our params with the new values.
484
236
        self.update_params(params.clone());
485
236

            
486
236
        self.vanguard_sets.remove_unlisted(netdir);
487
236

            
488
236
        // If we loaded some vanguards from persistent storage but we still need more,
489
236
        // we select them here.
490
236
        //
491
236
        // If full vanguards are not enabled and we started with an empty (default)
492
236
        // vanguard set, we populate the sets here.
493
236
        //
494
236
        // If we have already populated the vanguard sets in a previous iteration,
495
236
        // this will ensure they have enough vanguards.
496
236
        self.vanguard_sets
497
236
            .replenish_vanguards(runtime, netdir, &params, self.mode)?;
498

            
499
        // Flush the vanguard sets to disk.
500
236
        self.flush_to_storage(storage)?;
501

            
502
232
        Ok(())
503
236
    }
504

            
505
    /// Update our vanguard params.
506
2420
    fn update_params(&mut self, new_params: VanguardParams) {
507
2420
        self.params = new_params;
508
2420
    }
509

            
510
    /// Flush the vanguard sets to storage, if the mode is "vanguards-full".
511
2420
    fn flush_to_storage(
512
2420
        &self,
513
2420
        storage: &DynStorageHandle<VanguardSets>,
514
2420
    ) -> Result<(), VanguardMgrError> {
515
2420
        match self.mode {
516
1088
            VanguardMode::Lite | VanguardMode::Disabled => Ok(()),
517
            VanguardMode::Full => {
518
1332
                debug!("The vanguards may have changed; flushing to vanguard state file");
519
1332
                Ok(storage.store(&self.vanguard_sets)?)
520
            }
521
        }
522
2420
    }
523
}
524

            
525
#[cfg(any(test, feature = "testing"))]
526
use {
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"))]
533
impl VanguardMgr<MockRuntime> {
534
    /// Create a new VanguardMgr for testing.
535
1172
    pub fn new_testing(
536
1172
        rt: &MockRuntime,
537
1172
        mode: VanguardMode,
538
1172
    ) -> Result<Arc<VanguardMgr<MockRuntime>>, VanguardMgrError> {
539
1172
        let config = VanguardConfig {
540
1172
            mode: ExplicitOrAuto::Explicit(mode),
541
1172
        };
542
1172
        let statemgr = TestingStateMgr::new();
543
1172
        let lock = statemgr.try_lock()?;
544
1172
        assert!(lock.held());
545
        // TODO(#1382): has_onion_svc doesn't matter right now
546
1172
        let has_onion_svc = false;
547
1172
        Ok(Arc::new(VanguardMgr::new(
548
1172
            &config,
549
1172
            rt.clone(),
550
1172
            statemgr,
551
1172
            has_onion_svc,
552
1172
        )?))
553
1172
    }
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
1172
    pub async fn init_vanguard_sets(
560
1172
        self: &Arc<VanguardMgr<MockRuntime>>,
561
1172
        netdir: &NetDir,
562
1212
    ) -> Result<Arc<TestNetDirProvider>, VanguardMgrError> {
563
80
        let netdir_provider = Arc::new(TestNetDirProvider::new());
564
80
        self.launch_background_tasks(&(netdir_provider.clone() as Arc<dyn NetDirProvider>))?;
565
80
        self.runtime.progress_until_stalled().await;
566

            
567
        // Call set_netdir_and_notify to trigger an event
568
80
        netdir_provider
569
80
            .set_netdir_and_notify(Arc::new(netdir.clone()))
570
80
            .await;
571

            
572
        // Wait until the vanguard mgr has finished handling the netdir event.
573
80
        self.runtime.progress_until_stalled().await;
574

            
575
80
        Ok(netdir_provider)
576
80
    }
577
}
578

            
579
/// The vanguard layer.
580
#[derive(Debug, Clone, Copy, PartialEq)] //
581
#[derive(derive_more::Display)] //
582
#[non_exhaustive]
583
pub 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)]
593
mod 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_duration_subtraction)]
604
    #![allow(clippy::useless_vec)]
605
    #![allow(clippy::needless_pass_by_value)]
606
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
607

            
608
    use std::{fmt, time};
609

            
610
    use set::TimeBoundVanguard;
611
    use tor_config::ExplicitOrAuto;
612
    use tor_relay_selection::RelayExclusion;
613

            
614
    use super::*;
615

            
616
    use tor_basic_utils::test_rng::testing_rng;
617
    use tor_linkspec::{HasRelayIds, RelayIds};
618
    use tor_netdir::{
619
        testnet::{self, construct_custom_netdir_with_params},
620
        testprovider::TestNetDirProvider,
621
    };
622
    use tor_persist::FsStateMgr;
623
    use tor_rtmock::MockRuntime;
624
    use Layer::*;
625

            
626
    use itertools::Itertools;
627

            
628
    /// Enable lite vanguards for onion services.
629
    const ENABLE_LITE_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 1)];
630

            
631
    /// Enable full vanguards for hidden services.
632
    const ENABLE_FULL_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 2)];
633

            
634
    /// A valid vanguard state file.
635
    const VANGUARDS_JSON: &str = include_str!("../testdata/vanguards.json");
636

            
637
    /// A invalid vanguard state file.
638
    const INVALID_VANGUARDS_JSON: &str = include_str!("../testdata/vanguards_invalid.json");
639

            
640
    /// Create the `StateMgr`, populating the vanguards.json state file with the specified JSON string.
641
    fn state_dir_with_vanguards(vanguards_json: &str) -> (FsStateMgr, tempfile::TempDir) {
642
        let dir = tempfile::TempDir::new().unwrap();
643
        std::fs::create_dir_all(dir.path().join("state")).unwrap();
644
        std::fs::write(dir.path().join("state/vanguards.json"), vanguards_json).unwrap();
645

            
646
        let statemgr = FsStateMgr::from_path_and_mistrust(
647
            dir.path(),
648
            &fs_mistrust::Mistrust::new_dangerously_trust_everyone(),
649
        )
650
        .unwrap();
651

            
652
        (statemgr, dir)
653
    }
654

            
655
    impl fmt::Debug for Vanguard<'_> {
656
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
657
            f.debug_struct("Vanguard").finish()
658
        }
659
    }
660

            
661
    impl Inner {
662
        /// Return the L2 vanguard set.
663
        pub(super) fn l2_vanguards(&self) -> &Vec<TimeBoundVanguard> {
664
            self.vanguard_sets.l2_vanguards()
665
        }
666

            
667
        /// Return the L3 vanguard set.
668
        pub(super) fn l3_vanguards(&self) -> &Vec<TimeBoundVanguard> {
669
            self.vanguard_sets.l3_vanguards()
670
        }
671
    }
672

            
673
    /// Return a maximally permissive RelaySelector for a vanguard.
674
    fn permissive_selector() -> RelaySelector<'static> {
675
        RelaySelector::new(
676
            tor_relay_selection::RelayUsage::vanguard(),
677
            RelayExclusion::no_relays_excluded(),
678
        )
679
    }
680

            
681
    /// Look up the vanguard in the specified VanguardSet.
682
    fn find_in_set<R: Runtime>(
683
        relay_ids: &RelayIds,
684
        mgr: &VanguardMgr<R>,
685
        layer: Layer,
686
    ) -> Option<TimeBoundVanguard> {
687
        let inner = mgr.inner.read().unwrap();
688

            
689
        let vanguards = match layer {
690
            Layer2 => inner.l2_vanguards(),
691
            Layer3 => inner.l3_vanguards(),
692
        };
693

            
694
        // Look up the TimeBoundVanguard that corresponds to this Vanguard,
695
        // and figure out its expiry.
696
        vanguards.iter().find(|v| v.id == *relay_ids).cloned()
697
    }
698

            
699
    /// Get the total number of vanguard entries (L2 + L3).
700
    fn vanguard_count<R: Runtime>(mgr: &VanguardMgr<R>) -> usize {
701
        let inner = mgr.inner.read().unwrap();
702
        inner.l2_vanguards().len() + inner.l3_vanguards().len()
703
    }
704

            
705
    /// Return a `Duration` representing how long until this vanguard expires.
706
    fn duration_until_expiry<R: Runtime>(
707
        relay_ids: &RelayIds,
708
        mgr: &VanguardMgr<R>,
709
        runtime: &R,
710
        layer: Layer,
711
    ) -> Duration {
712
        // Look up the TimeBoundVanguard that corresponds to this Vanguard,
713
        // and figure out its expiry.
714
        let vanguard = find_in_set(relay_ids, mgr, layer).unwrap();
715

            
716
        vanguard
717
            .when
718
            .duration_since(runtime.wallclock())
719
            .unwrap_or_default()
720
    }
721

            
722
    /// Assert the lifetime of the specified `vanguard` is within the bounds of its `layer`.
723
    fn assert_expiry_in_bounds<R: Runtime>(
724
        vanguard: &Vanguard<'_>,
725
        mgr: &VanguardMgr<R>,
726
        runtime: &R,
727
        params: &VanguardParams,
728
        layer: Layer,
729
    ) {
730
        let (min, max) = match layer {
731
            Layer2 => (params.l2_lifetime_min(), params.l2_lifetime_max()),
732
            Layer3 => (params.l3_lifetime_min(), params.l3_lifetime_max()),
733
        };
734

            
735
        let vanguard = RelayIds::from_relay_ids(vanguard.relay());
736
        // This is not exactly the lifetime of the vanguard,
737
        // but rather the time left until it expires (but it's close enough for our purposes).
738
        let lifetime = duration_until_expiry(&vanguard, mgr, runtime, layer);
739

            
740
        assert!(
741
            lifetime >= min && lifetime <= max,
742
            "lifetime {lifetime:?} not between {min:?} and {max:?}",
743
        );
744
    }
745

            
746
    /// Assert that the vanguard manager's pools are empty.
747
    fn assert_sets_empty<R: Runtime>(vanguardmgr: &VanguardMgr<R>) {
748
        let inner = vanguardmgr.inner.read().unwrap();
749
        // The sets are initially empty, and the targets are set to 0
750
        assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
751
        assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
752
        assert_eq!(vanguard_count(vanguardmgr), 0);
753
    }
754

            
755
    /// Assert that the vanguard manager's pools have been filled.
756
    fn assert_sets_filled<R: Runtime>(vanguardmgr: &VanguardMgr<R>, params: &VanguardParams) {
757
        let inner = vanguardmgr.inner.read().unwrap();
758
        let l2_pool_size = params.l2_pool_size();
759
        // The sets are initially empty
760
        assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
761

            
762
        if inner.mode == VanguardMode::Full {
763
            assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
764
            let l3_pool_size = params.l3_pool_size();
765
            assert_eq!(vanguard_count(vanguardmgr), l2_pool_size + l3_pool_size);
766
        }
767
    }
768

            
769
    /// Assert the target size of the specified vanguard set matches the target from `params`.
770
    fn assert_set_vanguards_targets_match_params<R: Runtime>(
771
        mgr: &VanguardMgr<R>,
772
        params: &VanguardParams,
773
    ) {
774
        let inner = mgr.inner.read().unwrap();
775
        assert_eq!(
776
            inner.vanguard_sets.l2_vanguards_target(),
777
            params.l2_pool_size()
778
        );
779
        if inner.mode == VanguardMode::Full {
780
            assert_eq!(
781
                inner.vanguard_sets.l3_vanguards_target(),
782
                params.l3_pool_size()
783
            );
784
        }
785
    }
786

            
787
    #[test]
788
    fn full_vanguards_disabled() {
789
        MockRuntime::test_with_various(|rt| async move {
790
            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
791
            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
792
            let mut rng = testing_rng();
793
            // Wait until the vanguard manager has bootstrapped
794
            // (otherwise we'll get a BootstrapRequired error)
795
            let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
796

            
797
            // Cannot select an L3 vanguard when running in "Lite" mode.
798
            let err = vanguardmgr
799
                .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
800
                .unwrap_err();
801
            assert!(
802
                matches!(
803
                    err,
804
                    VanguardMgrError::LayerNotSupported {
805
                        layer: Layer::Layer3,
806
                        mode: VanguardMode::Lite
807
                    }
808
                ),
809
                "{err}"
810
            );
811
        });
812
    }
813

            
814
    #[test]
815
    fn background_task_not_spawned() {
816
        MockRuntime::test_with_various(|rt| async move {
817
            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
818
            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
819
            let mut rng = testing_rng();
820

            
821
            // The sets are initially empty
822
            assert_sets_empty(&vanguardmgr);
823

            
824
            // VanguardMgr::launch_background tasks was not called, so select_vanguard will return
825
            // an error (because the vanguard sets are empty)
826
            let err = vanguardmgr
827
                .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
828
                .unwrap_err();
829

            
830
            assert!(
831
                matches!(
832
                    err,
833
                    VanguardMgrError::BootstrapRequired {
834
                        action: "select vanguard"
835
                    }
836
                ),
837
                "{err:?}"
838
            );
839
        });
840
    }
841

            
842
    #[test]
843
    fn select_vanguards() {
844
        MockRuntime::test_with_various(|rt| async move {
845
            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Full).unwrap();
846

            
847
            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
848
            let params = VanguardParams::try_from(netdir.params()).unwrap();
849
            let mut rng = testing_rng();
850

            
851
            // The sets are initially empty
852
            assert_sets_empty(&vanguardmgr);
853

            
854
            // Wait until the vanguard manager has bootstrapped
855
            let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
856

            
857
            assert_sets_filled(&vanguardmgr, &params);
858

            
859
            let vanguard1 = vanguardmgr
860
                .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
861
                .unwrap();
862
            assert_expiry_in_bounds(&vanguard1, &vanguardmgr, &rt, &params, Layer2);
863

            
864
            let exclusion = RelayExclusion::exclude_identities(
865
                vanguard1
866
                    .relay()
867
                    .identities()
868
                    .map(|id| id.to_owned())
869
                    .collect(),
870
            );
871
            let selector =
872
                RelaySelector::new(tor_relay_selection::RelayUsage::vanguard(), exclusion);
873

            
874
            let vanguard2 = vanguardmgr
875
                .select_vanguard(&mut rng, &netdir, Layer3, &selector)
876
                .unwrap();
877

            
878
            assert_expiry_in_bounds(&vanguard2, &vanguardmgr, &rt, &params, Layer3);
879
            // Ensure we didn't select the same vanguard twice
880
            assert_ne!(
881
                vanguard1.relay().identities().collect_vec(),
882
                vanguard2.relay().identities().collect_vec()
883
            );
884
        });
885
    }
886

            
887
    /// Override the vanguard params from the netdir, returning the new VanguardParams.
888
    ///
889
    /// This also waits until the vanguard manager has had a chance to process the changes.
890
    async fn install_new_params(
891
        rt: &MockRuntime,
892
        netdir_provider: &TestNetDirProvider,
893
        params: impl IntoIterator<Item = (&str, i32)>,
894
    ) -> VanguardParams {
895
        let new_netdir = testnet::construct_custom_netdir_with_params(|_, _, _| {}, params, None)
896
            .unwrap()
897
            .unwrap_if_sufficient()
898
            .unwrap();
899
        let new_params = VanguardParams::try_from(new_netdir.params()).unwrap();
900

            
901
        netdir_provider.set_netdir_and_notify(new_netdir).await;
902

            
903
        // Wait until the vanguard mgr has finished handling the new netdir.
904
        rt.progress_until_stalled().await;
905

            
906
        new_params
907
    }
908

            
909
    /// Switch the vanguard "mode" of the VanguardMgr to `mode`,
910
    /// by setting the vanguards-hs-service parameter.
911
    //
912
    // TODO(#1382): use this instead of switch_hs_mode_config.
913
    #[allow(unused)]
914
    async fn switch_hs_mode(
915
        rt: &MockRuntime,
916
        vanguardmgr: &VanguardMgr<MockRuntime>,
917
        netdir_provider: &TestNetDirProvider,
918
        mode: VanguardMode,
919
    ) {
920
        use VanguardMode::*;
921

            
922
        let _params = match mode {
923
            Lite => install_new_params(rt, netdir_provider, ENABLE_LITE_VANGUARDS).await,
924
            Full => install_new_params(rt, netdir_provider, ENABLE_FULL_VANGUARDS).await,
925
            Disabled => panic!("cannot disable vanguards in the vanguard tests!"),
926
        };
927

            
928
        assert_eq!(vanguardmgr.mode(), mode);
929
    }
930

            
931
    /// Switch the vanguard "mode" of the VanguardMgr to `mode`,
932
    /// by calling `VanguardMgr::reconfigure`.
933
    fn switch_hs_mode_config(vanguardmgr: &VanguardMgr<MockRuntime>, mode: VanguardMode) {
934
        let _ = vanguardmgr
935
            .reconfigure(&VanguardConfig {
936
                mode: ExplicitOrAuto::Explicit(mode),
937
            })
938
            .unwrap();
939

            
940
        assert_eq!(vanguardmgr.mode(), mode);
941
    }
942

            
943
    /// Use a new NetDir that excludes one of our L2 vanguards
944
    async fn install_netdir_excluding_vanguard<'a>(
945
        runtime: &MockRuntime,
946
        vanguard: &Vanguard<'_>,
947
        params: impl IntoIterator<Item = (&'a str, i32)>,
948
        netdir_provider: &TestNetDirProvider,
949
    ) -> NetDir {
950
        let new_netdir = construct_custom_netdir_with_params(
951
            |_idx, bld, _| {
952
                let md_so_far = bld.md.testing_md().unwrap();
953
                if md_so_far.ed25519_id() == vanguard.relay().id() {
954
                    bld.omit_rs = true;
955
                }
956
            },
957
            params,
958
            None,
959
        )
960
        .unwrap()
961
        .unwrap_if_sufficient()
962
        .unwrap();
963

            
964
        netdir_provider
965
            .set_netdir_and_notify(new_netdir.clone())
966
            .await;
967
        // Wait until the vanguard mgr has finished handling the new netdir.
968
        runtime.progress_until_stalled().await;
969

            
970
        new_netdir
971
    }
972

            
973
    #[test]
974
    fn override_vanguard_set_size() {
975
        MockRuntime::test_with_various(|rt| async move {
976
            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
977
            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
978
            // Wait until the vanguard manager has bootstrapped
979
            let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
980

            
981
            let params = VanguardParams::try_from(netdir.params()).unwrap();
982
            let old_size = params.l2_pool_size();
983
            assert_set_vanguards_targets_match_params(&vanguardmgr, &params);
984

            
985
            const PARAMS: [[(&str, i32); 2]; 2] = [
986
                [("guard-hs-l2-number", 1), ("guard-hs-l3-number", 10)],
987
                [("guard-hs-l2-number", 10), ("guard-hs-l3-number", 10)],
988
            ];
989

            
990
            for params in PARAMS {
991
                let new_params = install_new_params(&rt, &netdir_provider, params).await;
992

            
993
                // Ensure the target size was updated.
994
                assert_set_vanguards_targets_match_params(&vanguardmgr, &new_params);
995
                {
996
                    let inner = vanguardmgr.inner.read().unwrap();
997
                    let l2_vanguards = inner.l2_vanguards();
998
                    let l3_vanguards = inner.l3_vanguards();
999
                    let new_l2_size = params[0].1 as usize;
                    if new_l2_size < old_size {
                        // The actual size of the set hasn't changed: it's OK to have more vanguards than
                        // needed in the set (they extraneous ones will eventually expire).
                        assert_eq!(l2_vanguards.len(), old_size);
                    } else {
                        // The new size is greater, so we have more L2 vanguards now.
                        assert_eq!(l2_vanguards.len(), new_l2_size);
                    }
                    // There are no L3 vanguards because full vanguards are not in use.
                    assert_eq!(l3_vanguards.len(), 0);
                }
            }
        });
    }
    #[test]
    fn expire_vanguards() {
        MockRuntime::test_with_various(|rt| async move {
            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
            let params = VanguardParams::try_from(netdir.params()).unwrap();
            let initial_l2_number = params.l2_pool_size();
            // Wait until the vanguard manager has bootstrapped
            let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
            assert_eq!(vanguard_count(&vanguardmgr), params.l2_pool_size());
            // Find the RelayIds of the vanguard that is due to expire next
            let vanguard_id = {
                let inner = vanguardmgr.inner.read().unwrap();
                let next_expiry = inner.vanguard_sets.next_expiry().unwrap();
                inner
                    .l2_vanguards()
                    .iter()
                    .find(|v| v.when == next_expiry)
                    .cloned()
                    .unwrap()
                    .id
            };
            const FEWER_VANGUARDS_PARAM: [(&str, i32); 1] = [("guard-hs-l2-number", 1)];
            // Set the number of L2 vanguards to a lower value to ensure the vanguard that is about
            // to expire is not replaced. This allows us to test that it has indeed expired
            // (we can't simply check that the relay is no longer is the set,
            // because it's possible for the set to get replenished with the same relay).
            let new_params = install_new_params(&rt, &netdir_provider, FEWER_VANGUARDS_PARAM).await;
            // The vanguard has not expired yet.
            let timebound_vanguard = find_in_set(&vanguard_id, &vanguardmgr, Layer2);
            assert!(timebound_vanguard.is_some());
            assert_eq!(vanguard_count(&vanguardmgr), initial_l2_number);
            let lifetime = duration_until_expiry(&vanguard_id, &vanguardmgr, &rt, Layer2);
            // Wait until this vanguard expires
            rt.advance_by(lifetime).await.unwrap();
            rt.progress_until_stalled().await;
            let timebound_vanguard = find_in_set(&vanguard_id, &vanguardmgr, Layer2);
            // The vanguard expired, but was not replaced.
            assert!(timebound_vanguard.is_none());
            assert_eq!(vanguard_count(&vanguardmgr), initial_l2_number - 1);
            // Wait until more vanguards expire. This will reduce the set size to 1
            // (the new target size we set by overriding the params).
            for _ in 0..initial_l2_number - 1 {
                let vanguard_id = {
                    let inner = vanguardmgr.inner.read().unwrap();
                    let next_expiry = inner.vanguard_sets.next_expiry().unwrap();
                    inner
                        .l2_vanguards()
                        .iter()
                        .find(|v| v.when == next_expiry)
                        .cloned()
                        .unwrap()
                        .id
                };
                let lifetime = duration_until_expiry(&vanguard_id, &vanguardmgr, &rt, Layer2);
                rt.advance_by(lifetime).await.unwrap();
                rt.progress_until_stalled().await;
            }
            assert_eq!(vanguard_count(&vanguardmgr), new_params.l2_pool_size());
            // Update the L2 set size again, to force the vanguard manager to replenish the L2 set.
            const MORE_VANGUARDS_PARAM: [(&str, i32); 1] = [("guard-hs-l2-number", 5)];
            // Set the number of L2 vanguards to a lower value to ensure the vanguard that is about
            // to expire is not replaced. This allows us to test that it has indeed expired
            // (we can't simply check that the relay is no longer is the set,
            // because it's possible for the set to get replenished with the same relay).
            let new_params = install_new_params(&rt, &netdir_provider, MORE_VANGUARDS_PARAM).await;
            // Check that we replaced the expired vanguard with a new one:
            assert_eq!(vanguard_count(&vanguardmgr), new_params.l2_pool_size());
            {
                let inner = vanguardmgr.inner.read().unwrap();
                let l2_count = inner.l2_vanguards().len();
                assert_eq!(l2_count, new_params.l2_pool_size());
            }
        });
    }
    #[test]
    fn full_vanguards_persistence() {
        MockRuntime::test_with_various(|rt| async move {
            let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
            let netdir =
                construct_custom_netdir_with_params(|_, _, _| {}, ENABLE_LITE_VANGUARDS, None)
                    .unwrap()
                    .unwrap_if_sufficient()
                    .unwrap();
            let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
            // Full vanguards are not enabled, so we don't expect anything to be written
            // to persistent storage.
            assert_eq!(vanguardmgr.mode(), VanguardMode::Lite);
            assert!(vanguardmgr.storage.load().unwrap().is_none());
            let mut rng = testing_rng();
            assert!(vanguardmgr
                .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
                .is_err());
            // Enable full vanguards again.
            //
            // We expect VanguardMgr to populate the L3 set, and write the VanguardSets to storage.
            switch_hs_mode_config(&vanguardmgr, VanguardMode::Full);
            rt.progress_until_stalled().await;
            let vanguard_sets_orig = vanguardmgr.storage.load().unwrap();
            assert!(vanguardmgr
                .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
                .is_ok());
            // Switch to lite vanguards.
            switch_hs_mode_config(&vanguardmgr, VanguardMode::Lite);
            // The vanguard sets should not change when switching between lite and full vanguards.
            assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
            switch_hs_mode_config(&vanguardmgr, VanguardMode::Full);
            assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
            // TODO HS-VANGUARDS: we may want to disable the ability to switch back to lite
            // vanguards.
            // Switch to lite vanguards and remove a relay from the consensus.
            // The relay should *not* be persisted to storage until we switch back to full
            // vanguards.
            switch_hs_mode_config(&vanguardmgr, VanguardMode::Lite);
            let mut rng = testing_rng();
            let excluded_vanguard = vanguardmgr
                .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
                .unwrap();
            let _ = install_netdir_excluding_vanguard(
                &rt,
                &excluded_vanguard,
                ENABLE_LITE_VANGUARDS,
                &netdir_provider,
            )
            .await;
            // The vanguard sets from storage haven't changed, because we are in "lite" mode.
            assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
            let _ = install_netdir_excluding_vanguard(
                &rt,
                &excluded_vanguard,
                ENABLE_FULL_VANGUARDS,
                &netdir_provider,
            )
            .await;
        });
    }
    #[test]
    fn load_from_state_file() {
        MockRuntime::test_with_various(|rt| async move {
            // Set the wallclock to a time when some of the stored vanguards are still valid.
            let now = time::UNIX_EPOCH + Duration::from_secs(1610000000);
            rt.jump_wallclock(now);
            let config = VanguardConfig {
                mode: ExplicitOrAuto::Explicit(VanguardMode::Full),
            };
            // The state file contains no vanguards
            let (statemgr, _dir) =
                state_dir_with_vanguards(r#"{ "l2_vanguards": [], "l3_vanguards": [] }"#);
            let vanguardmgr = VanguardMgr::new(&config, rt.clone(), statemgr, false).unwrap();
            {
                let inner = vanguardmgr.inner.read().unwrap();
                // The vanguard sets should be empty too
                assert!(inner.vanguard_sets.l2().is_empty());
                assert!(inner.vanguard_sets.l3().is_empty());
            }
            let (statemgr, _dir) = state_dir_with_vanguards(VANGUARDS_JSON);
            let vanguardmgr =
                Arc::new(VanguardMgr::new(&config, rt.clone(), statemgr, false).unwrap());
            let (initial_l2, initial_l3) = {
                let inner = vanguardmgr.inner.read().unwrap();
                let l2_vanguards = inner.vanguard_sets.l2_vanguards().clone();
                let l3_vanguards = inner.vanguard_sets.l3_vanguards().clone();
                // The sets actually contain 4 and 5 vanguards, respectively,
                // but the expired ones are discarded.
                assert_eq!(l2_vanguards.len(), 3);
                assert_eq!(l3_vanguards.len(), 2);
                // We don't know how many vanguards we're going to need
                // until we fetch the consensus.
                assert_eq!(inner.vanguard_sets.l2_vanguards_target(), 0);
                assert_eq!(inner.vanguard_sets.l3_vanguards_target(), 0);
                assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
                assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
                (l2_vanguards, l3_vanguards)
            };
            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
            let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
            {
                let inner = vanguardmgr.inner.read().unwrap();
                let l2_vanguards = inner.vanguard_sets.l2_vanguards();
                let l3_vanguards = inner.vanguard_sets.l3_vanguards();
                // The sets were replenished with more vanguards
                assert_eq!(l2_vanguards.len(), 4);
                assert_eq!(l3_vanguards.len(), 8);
                // We now know we need 4 L2 vanguards and 8 L3 ones.
                assert_eq!(inner.vanguard_sets.l2_vanguards_target(), 4);
                assert_eq!(inner.vanguard_sets.l3_vanguards_target(), 8);
                assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
                assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
                // All of the vanguards read from the state file should still be in the sets.
                assert!(initial_l2.iter().all(|v| l2_vanguards.contains(v)));
                assert!(initial_l3.iter().all(|v| l3_vanguards.contains(v)));
            }
        });
    }
    #[test]
    fn invalid_state_file() {
        MockRuntime::test_with_various(|rt| async move {
            let config = VanguardConfig {
                mode: ExplicitOrAuto::Explicit(VanguardMode::Full),
            };
            let (statemgr, _dir) = state_dir_with_vanguards(INVALID_VANGUARDS_JSON);
            let res = VanguardMgr::new(&config, rt.clone(), statemgr, false);
            assert!(matches!(res, Err(VanguardMgrError::State(_))));
        });
    }
}