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 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
39const STORAGE_KEY: &str = "vanguards";
41
42pub struct VanguardMgr<R: Runtime> {
44 inner: RwLock<Inner>,
46 runtime: R,
48 storage: DynStorageHandle<VanguardSets>,
51}
52
53struct Inner {
55 params: VanguardParams,
57 mode: VanguardMode,
62 vanguard_sets: VanguardSets,
96 #[allow(unused)]
101 has_onion_svc: bool,
102 config_tx: watch::Sender<VanguardConfig>,
104}
105
106#[derive(Copy, Clone, Debug)]
111enum ShutdownStatus {
112 Continue,
114 Terminate,
116}
117
118impl<R: Runtime> VanguardMgr<R> {
119 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 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 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 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 #[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 pub fn reconfigure(&self, config: &VanguardConfig) -> Result<RetireCircuits, ReconfigureError> {
203 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 inner.config_tx.maybe_send(|_| config.clone());
218
219 Ok(RetireCircuits::All)
220 } else {
221 Ok(RetireCircuits::None)
222 }
223 }
224
225 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 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 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 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 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 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 Ok(ShutdownStatus::Continue)
401 },
402 }
403 }
404
405 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 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 inner.update_vanguard_sets(&self.runtime, &self.storage, &netdir)?;
441 }
442
443 let Some(expiry) = inner.vanguard_sets.next_expiry() else {
444 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 pub fn mode(&self) -> VanguardMode {
456 self.inner.read().expect("poisoned lock").mode
457 }
458}
459
460impl Inner {
461 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 self.update_params(params.clone());
485
486 self.vanguard_sets.remove_unlisted(netdir);
487
488 self.vanguard_sets
497 .replenish_vanguards(runtime, netdir, ¶ms, self.mode)?;
498
499 self.flush_to_storage(storage)?;
501
502 Ok(())
503 }
504
505 fn update_params(&mut self, new_params: VanguardParams) {
507 self.params = new_params;
508 }
509
510 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#[cfg(any(test, feature = "testing"))]
533impl VanguardMgr<MockRuntime> {
534 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 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 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 netdir_provider
569 .set_netdir_and_notify(Arc::new(netdir.clone()))
570 .await;
571
572 self.runtime.progress_until_stalled().await;
574
575 Ok(netdir_provider)
576 }
577}
578
579#[derive(Debug, Clone, Copy, PartialEq)] #[derive(derive_more::Display)] #[non_exhaustive]
583pub enum Layer {
584 #[display("layer 2")]
586 Layer2,
587 #[display("layer 3")]
589 Layer3,
590}
591
592#[cfg(test)]
593mod test {
594 #![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)] 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}