1pub 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 rand::RngCore;
20use tor_rtcompat::SpawnExt as _;
21
22use tor_async_utils::PostageWatchSenderExt as _;
23use tor_config::ReconfigureError;
24use tor_error::{error_report, internal, into_internal};
25use tor_netdir::{DirEvent, NetDir, NetDirProvider, Timeliness};
26use tor_persist::{DynStorageHandle, StateMgr};
27use tor_relay_selection::RelaySelector;
28use tor_rtcompat::Runtime;
29use tracing::{debug, info, instrument};
30
31use crate::{RetireCircuits, VanguardMode};
32
33use set::VanguardSets;
34
35use crate::VanguardConfig;
36pub use config::VanguardParams;
37pub use err::VanguardMgrError;
38pub use set::Vanguard;
39
40const STORAGE_KEY: &str = "vanguards";
42
43pub struct VanguardMgr<R: Runtime> {
45 inner: RwLock<Inner>,
47 runtime: R,
49 storage: DynStorageHandle<VanguardSets>,
52}
53
54struct Inner {
56 params: VanguardParams,
58 mode: VanguardMode,
63 vanguard_sets: VanguardSets,
97 #[allow(unused)]
102 has_onion_svc: bool,
103 config_tx: watch::Sender<VanguardConfig>,
105}
106
107#[derive(Copy, Clone, Debug)]
112enum ShutdownStatus {
113 Continue,
115 Terminate,
117}
118
119impl<R: Runtime> VanguardMgr<R> {
120 pub fn new<S>(
124 config: &VanguardConfig,
125 runtime: R,
126 state_mgr: S,
127 has_onion_svc: bool,
128 ) -> Result<Self, VanguardMgrError>
129 where
130 S: StateMgr + Send + Sync + 'static,
131 {
132 let params = VanguardParams::default();
135 let storage: DynStorageHandle<VanguardSets> = state_mgr.create_handle(STORAGE_KEY);
136
137 let vanguard_sets = match storage.load()? {
138 Some(mut sets) => {
139 info!("Loading vanguards from vanguard state file");
140 let now = runtime.wallclock();
142 let _ = sets.remove_expired(now);
143 sets
144 }
145 None => {
146 debug!("Vanguard state file not found, selecting new vanguards");
147 Default::default()
152 }
153 };
154
155 let (config_tx, _config_rx) = watch::channel();
156 let inner = Inner {
157 params,
158 mode: config.mode(),
159 vanguard_sets,
160 has_onion_svc,
161 config_tx,
162 };
163
164 Ok(Self {
165 inner: RwLock::new(inner),
166 runtime,
167 storage,
168 })
169 }
170
171 #[instrument(level = "trace", skip_all)]
177 pub fn launch_background_tasks(
178 self: &Arc<Self>,
179 netdir_provider: &Arc<dyn NetDirProvider>,
180 ) -> Result<(), VanguardMgrError>
181 where
182 R: Runtime,
183 {
184 let netdir_provider = Arc::clone(netdir_provider);
185 let config_rx = self
186 .inner
187 .write()
188 .expect("poisoned lock")
189 .config_tx
190 .subscribe();
191 self.runtime
192 .spawn(Self::maintain_vanguard_sets(
193 Arc::downgrade(self),
194 Arc::downgrade(&netdir_provider),
195 config_rx,
196 ))
197 .map_err(|e| VanguardMgrError::Spawn(Arc::new(e)))?;
198
199 Ok(())
200 }
201
202 pub fn reconfigure(&self, config: &VanguardConfig) -> Result<RetireCircuits, ReconfigureError> {
204 let mut inner = self.inner.write().expect("poisoned lock");
213 let new_mode = config.mode();
214 if new_mode != inner.mode {
215 inner.mode = new_mode;
216
217 inner.config_tx.maybe_send(|_| config.clone());
219
220 Ok(RetireCircuits::All)
221 } else {
222 Ok(RetireCircuits::None)
223 }
224 }
225
226 pub fn select_vanguard<'a, Rng: RngCore>(
265 &self,
266 rng: &mut Rng,
267 netdir: &'a NetDir,
268 layer: Layer,
269 relay_selector: &RelaySelector<'a>,
270 ) -> Result<Vanguard<'a>, VanguardMgrError> {
271 use VanguardMode::*;
272
273 let inner = self.inner.read().expect("poisoned lock");
274
275 if inner.vanguard_sets.l2().is_empty() && inner.vanguard_sets.l3().is_empty() {
279 return Err(VanguardMgrError::BootstrapRequired {
280 action: "select vanguard",
281 });
282 }
283
284 let relay =
285 match (layer, inner.mode) {
286 (Layer::Layer2, Full) | (Layer::Layer2, Lite) => inner
287 .vanguard_sets
288 .l2()
289 .pick_relay(rng, netdir, relay_selector),
290 (Layer::Layer3, Full) => {
291 inner
292 .vanguard_sets
293 .l3()
294 .pick_relay(rng, netdir, relay_selector)
295 }
296 _ => {
297 return Err(VanguardMgrError::LayerNotSupported {
298 layer,
299 mode: inner.mode,
300 });
301 }
302 };
303
304 relay.ok_or(VanguardMgrError::NoSuitableRelay(layer))
305 }
306
307 async fn maintain_vanguard_sets(
315 mgr: Weak<Self>,
316 netdir_provider: Weak<dyn NetDirProvider>,
317 mut config_rx: watch::Receiver<VanguardConfig>,
318 ) {
319 let mut netdir_events = match netdir_provider.upgrade() {
320 Some(provider) => provider.events(),
321 None => {
322 return;
323 }
324 };
325
326 loop {
327 match Self::run_once(
328 Weak::clone(&mgr),
329 Weak::clone(&netdir_provider),
330 &mut netdir_events,
331 &mut config_rx,
332 )
333 .await
334 {
335 Ok(ShutdownStatus::Continue) => continue,
336 Ok(ShutdownStatus::Terminate) => {
337 debug!("Vanguard manager is shutting down");
338 break;
339 }
340 Err(e) => {
341 error_report!(e, "Vanguard manager crashed");
342 break;
343 }
344 }
345 }
346 }
347
348 async fn run_once(
356 mgr: Weak<Self>,
357 netdir_provider: Weak<dyn NetDirProvider>,
358 netdir_events: &mut BoxStream<'static, DirEvent>,
359 config_rx: &mut watch::Receiver<VanguardConfig>,
360 ) -> Result<ShutdownStatus, VanguardMgrError> {
361 let (mgr, netdir_provider) = match (mgr.upgrade(), netdir_provider.upgrade()) {
362 (Some(mgr), Some(netdir_provider)) => (mgr, netdir_provider),
363 _ => return Ok(ShutdownStatus::Terminate),
364 };
365
366 let now = mgr.runtime.wallclock();
367 let next_to_expire = mgr.rotate_expired(&netdir_provider, now)?;
368 let sleep_fut = async {
370 if let Some(dur) = next_to_expire {
371 let () = mgr.runtime.sleep(dur).await;
372 } else {
373 future::pending::<()>().await;
374 }
375 };
376
377 select_biased! {
378 event = netdir_events.next().fuse() => {
379 if let Some(DirEvent::NewConsensus) = event {
380 let netdir = netdir_provider.netdir(Timeliness::Timely)?;
381 mgr.inner.write().expect("poisoned lock")
382 .update_vanguard_sets(&mgr.runtime, &mgr.storage, &netdir)?;
383 }
384
385 Ok(ShutdownStatus::Continue)
386 },
387 _config = config_rx.recv().fuse() => {
388 if let Some(netdir) = Self::timely_netdir(&netdir_provider)? {
389 mgr.inner.write().expect("poisoned lock")
394 .update_vanguard_sets(&mgr.runtime, &mgr.storage, &netdir)?;
395 }
396
397 Ok(ShutdownStatus::Continue)
398 },
399 () = sleep_fut.fuse() => {
400 Ok(ShutdownStatus::Continue)
402 },
403 }
404 }
405
406 fn timely_netdir(
410 netdir_provider: &Arc<dyn NetDirProvider>,
411 ) -> Result<Option<Arc<NetDir>>, VanguardMgrError> {
412 use tor_netdir::Error as NetDirError;
413
414 match netdir_provider.netdir(Timeliness::Timely) {
415 Ok(netdir) => Ok(Some(netdir)),
416 Err(NetDirError::NoInfo) | Err(NetDirError::NotEnoughInfo) => Ok(None),
417 Err(e) => Err(e.into()),
418 }
419 }
420
421 fn rotate_expired(
425 &self,
426 netdir_provider: &Arc<dyn NetDirProvider>,
427 now: SystemTime,
428 ) -> Result<Option<Duration>, VanguardMgrError> {
429 let mut inner = self.inner.write().expect("poisoned lock");
430 let inner = &mut *inner;
431
432 let vanguard_sets = &mut inner.vanguard_sets;
433 let expired_count = vanguard_sets.remove_expired(now);
434
435 if expired_count > 0 {
436 info!("Rotating vanguards");
437 }
438
439 if let Some(netdir) = Self::timely_netdir(netdir_provider)? {
440 inner.update_vanguard_sets(&self.runtime, &self.storage, &netdir)?;
442 }
443
444 let Some(expiry) = inner.vanguard_sets.next_expiry() else {
445 return Ok(None);
447 };
448
449 expiry
450 .duration_since(now)
451 .map_err(|_| internal!("when > now, but now is later than when?!").into())
452 .map(Some)
453 }
454
455 pub fn mode(&self) -> VanguardMode {
457 self.inner.read().expect("poisoned lock").mode
458 }
459}
460
461impl Inner {
462 fn update_vanguard_sets<R: Runtime>(
476 &mut self,
477 runtime: &R,
478 storage: &DynStorageHandle<VanguardSets>,
479 netdir: &Arc<NetDir>,
480 ) -> Result<(), VanguardMgrError> {
481 let params = VanguardParams::try_from(netdir.params())
482 .map_err(into_internal!("invalid NetParameters"))?;
483
484 self.update_params(params.clone());
486
487 self.vanguard_sets.remove_unlisted(netdir);
488
489 self.vanguard_sets
498 .replenish_vanguards(runtime, netdir, ¶ms, self.mode)?;
499
500 self.flush_to_storage(storage)?;
502
503 Ok(())
504 }
505
506 fn update_params(&mut self, new_params: VanguardParams) {
508 self.params = new_params;
509 }
510
511 fn flush_to_storage(
513 &self,
514 storage: &DynStorageHandle<VanguardSets>,
515 ) -> Result<(), VanguardMgrError> {
516 match self.mode {
517 VanguardMode::Lite | VanguardMode::Disabled => Ok(()),
518 VanguardMode::Full => {
519 debug!("The vanguards may have changed; flushing to vanguard state file");
520 Ok(storage.store(&self.vanguard_sets)?)
521 }
522 }
523 }
524}
525
526#[cfg(any(test, feature = "testing"))]
527use {
528 tor_config::ExplicitOrAuto, tor_netdir::testprovider::TestNetDirProvider,
529 tor_persist::TestingStateMgr, tor_rtmock::MockRuntime,
530};
531
532#[cfg(any(test, feature = "testing"))]
534impl VanguardMgr<MockRuntime> {
535 pub fn new_testing(
537 rt: &MockRuntime,
538 mode: VanguardMode,
539 ) -> Result<Arc<VanguardMgr<MockRuntime>>, VanguardMgrError> {
540 let config = VanguardConfig {
541 mode: ExplicitOrAuto::Explicit(mode),
542 };
543 let statemgr = TestingStateMgr::new();
544 let lock = statemgr.try_lock()?;
545 assert!(lock.held());
546 let has_onion_svc = false;
548 Ok(Arc::new(VanguardMgr::new(
549 &config,
550 rt.clone(),
551 statemgr,
552 has_onion_svc,
553 )?))
554 }
555
556 pub async fn init_vanguard_sets(
561 self: &Arc<VanguardMgr<MockRuntime>>,
562 netdir: &NetDir,
563 ) -> Result<Arc<TestNetDirProvider>, VanguardMgrError> {
564 let netdir_provider = Arc::new(TestNetDirProvider::new());
565 self.launch_background_tasks(&(netdir_provider.clone() as Arc<dyn NetDirProvider>))?;
566 self.runtime.progress_until_stalled().await;
567
568 netdir_provider
570 .set_netdir_and_notify(Arc::new(netdir.clone()))
571 .await;
572
573 self.runtime.progress_until_stalled().await;
575
576 Ok(netdir_provider)
577 }
578}
579
580#[derive(Debug, Clone, Copy, PartialEq)] #[derive(derive_more::Display)] #[non_exhaustive]
584pub enum Layer {
585 #[display("layer 2")]
587 Layer2,
588 #[display("layer 3")]
590 Layer3,
591}
592
593#[cfg(test)]
594mod test {
595 #![allow(clippy::bool_assert_comparison)]
597 #![allow(clippy::clone_on_copy)]
598 #![allow(clippy::dbg_macro)]
599 #![allow(clippy::mixed_attributes_style)]
600 #![allow(clippy::print_stderr)]
601 #![allow(clippy::print_stdout)]
602 #![allow(clippy::single_char_pattern)]
603 #![allow(clippy::unwrap_used)]
604 #![allow(clippy::unchecked_time_subtraction)]
605 #![allow(clippy::useless_vec)]
606 #![allow(clippy::needless_pass_by_value)]
607 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 const ENABLE_LITE_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 1)];
631
632 const ENABLE_FULL_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 2)];
634
635 const VANGUARDS_JSON: &str = include_str!("../testdata/vanguards.json");
637
638 const INVALID_VANGUARDS_JSON: &str = include_str!("../testdata/vanguards_invalid.json");
640
641 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 pub(super) fn l2_vanguards(&self) -> &Vec<TimeBoundVanguard> {
665 self.vanguard_sets.l2_vanguards()
666 }
667
668 pub(super) fn l3_vanguards(&self) -> &Vec<TimeBoundVanguard> {
670 self.vanguard_sets.l3_vanguards()
671 }
672 }
673
674 fn permissive_selector() -> RelaySelector<'static> {
676 RelaySelector::new(
677 tor_relay_selection::RelayUsage::vanguard(),
678 RelayExclusion::no_relays_excluded(),
679 )
680 }
681
682 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 vanguards.iter().find(|v| v.id == *relay_ids).cloned()
698 }
699
700 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 fn duration_until_expiry<R: Runtime>(
708 relay_ids: &RelayIds,
709 mgr: &VanguardMgr<R>,
710 runtime: &R,
711 layer: Layer,
712 ) -> Duration {
713 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 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 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 fn assert_sets_empty<R: Runtime>(vanguardmgr: &VanguardMgr<R>) {
749 let inner = vanguardmgr.inner.read().unwrap();
750 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 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 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 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 let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
797
798 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 assert_sets_empty(&vanguardmgr);
824
825 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 assert_sets_empty(&vanguardmgr);
854
855 let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
857
858 assert_sets_filled(&vanguardmgr, ¶ms);
859
860 let vanguard1 = vanguardmgr
861 .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
862 .unwrap();
863 assert_expiry_in_bounds(&vanguard1, &vanguardmgr, &rt, ¶ms, 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, ¶ms, Layer3);
880 assert_ne!(
882 vanguard1.relay().identities().collect_vec(),
883 vanguard2.relay().identities().collect_vec()
884 );
885 });
886 }
887
888 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 rt.progress_until_stalled().await;
906
907 new_params
908 }
909
910 #[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 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 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 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 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, ¶ms);
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 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 assert_eq!(l2_vanguards.len(), old_size);
1005 } else {
1006 assert_eq!(l2_vanguards.len(), new_l2_size);
1008 }
1009 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 let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
1026 assert_eq!(vanguard_count(&vanguardmgr), params.l2_pool_size());
1027
1028 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 let new_params = install_new_params(&rt, &netdir_provider, FEWER_VANGUARDS_PARAM).await;
1047
1048 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 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 assert!(timebound_vanguard.is_none());
1062 assert_eq!(vanguard_count(&vanguardmgr), initial_l2_number - 1);
1063
1064 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 const MORE_VANGUARDS_PARAM: [(&str, i32); 1] = [("guard-hs-l2-number", 5)];
1088 let new_params = install_new_params(&rt, &netdir_provider, MORE_VANGUARDS_PARAM).await;
1093
1094 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 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 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_hs_mode_config(&vanguardmgr, VanguardMode::Lite);
1144
1145 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 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 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 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 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 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 assert_eq!(l2_vanguards.len(), 3);
1217 assert_eq!(l3_vanguards.len(), 2);
1218 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 assert_eq!(l2_vanguards.len(), 4);
1237 assert_eq!(l3_vanguards.len(), 8);
1238 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 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}