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::task::SpawnExt as _;
16use futures::{future, FutureExt as _};
17use futures::{select_biased, StreamExt as _};
18use postage::stream::Stream as _;
19use postage::watch;
20use rand::RngCore;
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};
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 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: RngCore>(
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_duration_subtraction)]
604 #![allow(clippy::useless_vec)]
605 #![allow(clippy::needless_pass_by_value)]
606 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 const ENABLE_LITE_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 1)];
630
631 const ENABLE_FULL_VANGUARDS: [(&str, i32); 1] = [("vanguards-hs-service", 2)];
633
634 const VANGUARDS_JSON: &str = include_str!("../testdata/vanguards.json");
636
637 const INVALID_VANGUARDS_JSON: &str = include_str!("../testdata/vanguards_invalid.json");
639
640 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 pub(super) fn l2_vanguards(&self) -> &Vec<TimeBoundVanguard> {
664 self.vanguard_sets.l2_vanguards()
665 }
666
667 pub(super) fn l3_vanguards(&self) -> &Vec<TimeBoundVanguard> {
669 self.vanguard_sets.l3_vanguards()
670 }
671 }
672
673 fn permissive_selector() -> RelaySelector<'static> {
675 RelaySelector::new(
676 tor_relay_selection::RelayUsage::vanguard(),
677 RelayExclusion::no_relays_excluded(),
678 )
679 }
680
681 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 vanguards.iter().find(|v| v.id == *relay_ids).cloned()
697 }
698
699 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 fn duration_until_expiry<R: Runtime>(
707 relay_ids: &RelayIds,
708 mgr: &VanguardMgr<R>,
709 runtime: &R,
710 layer: Layer,
711 ) -> Duration {
712 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 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 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 fn assert_sets_empty<R: Runtime>(vanguardmgr: &VanguardMgr<R>) {
748 let inner = vanguardmgr.inner.read().unwrap();
749 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 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 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 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 let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
796
797 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 assert_sets_empty(&vanguardmgr);
823
824 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 assert_sets_empty(&vanguardmgr);
853
854 let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
856
857 assert_sets_filled(&vanguardmgr, ¶ms);
858
859 let vanguard1 = vanguardmgr
860 .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
861 .unwrap();
862 assert_expiry_in_bounds(&vanguard1, &vanguardmgr, &rt, ¶ms, 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, ¶ms, Layer3);
879 assert_ne!(
881 vanguard1.relay().identities().collect_vec(),
882 vanguard2.relay().identities().collect_vec()
883 );
884 });
885 }
886
887 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 rt.progress_until_stalled().await;
905
906 new_params
907 }
908
909 #[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 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 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 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 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, ¶ms);
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 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;
1000 if new_l2_size < old_size {
1001 assert_eq!(l2_vanguards.len(), old_size);
1004 } else {
1005 assert_eq!(l2_vanguards.len(), new_l2_size);
1007 }
1008 assert_eq!(l3_vanguards.len(), 0);
1010 }
1011 }
1012 });
1013 }
1014
1015 #[test]
1016 fn expire_vanguards() {
1017 MockRuntime::test_with_various(|rt| async move {
1018 let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
1019 let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
1020 let params = VanguardParams::try_from(netdir.params()).unwrap();
1021 let initial_l2_number = params.l2_pool_size();
1022
1023 let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
1025 assert_eq!(vanguard_count(&vanguardmgr), params.l2_pool_size());
1026
1027 let vanguard_id = {
1029 let inner = vanguardmgr.inner.read().unwrap();
1030 let next_expiry = inner.vanguard_sets.next_expiry().unwrap();
1031 inner
1032 .l2_vanguards()
1033 .iter()
1034 .find(|v| v.when == next_expiry)
1035 .cloned()
1036 .unwrap()
1037 .id
1038 };
1039
1040 const FEWER_VANGUARDS_PARAM: [(&str, i32); 1] = [("guard-hs-l2-number", 1)];
1041 let new_params = install_new_params(&rt, &netdir_provider, FEWER_VANGUARDS_PARAM).await;
1046
1047 let timebound_vanguard = find_in_set(&vanguard_id, &vanguardmgr, Layer2);
1049 assert!(timebound_vanguard.is_some());
1050 assert_eq!(vanguard_count(&vanguardmgr), initial_l2_number);
1051
1052 let lifetime = duration_until_expiry(&vanguard_id, &vanguardmgr, &rt, Layer2);
1053 rt.advance_by(lifetime).await.unwrap();
1055 rt.progress_until_stalled().await;
1056
1057 let timebound_vanguard = find_in_set(&vanguard_id, &vanguardmgr, Layer2);
1058
1059 assert!(timebound_vanguard.is_none());
1061 assert_eq!(vanguard_count(&vanguardmgr), initial_l2_number - 1);
1062
1063 for _ in 0..initial_l2_number - 1 {
1066 let vanguard_id = {
1067 let inner = vanguardmgr.inner.read().unwrap();
1068 let next_expiry = inner.vanguard_sets.next_expiry().unwrap();
1069 inner
1070 .l2_vanguards()
1071 .iter()
1072 .find(|v| v.when == next_expiry)
1073 .cloned()
1074 .unwrap()
1075 .id
1076 };
1077 let lifetime = duration_until_expiry(&vanguard_id, &vanguardmgr, &rt, Layer2);
1078 rt.advance_by(lifetime).await.unwrap();
1079
1080 rt.progress_until_stalled().await;
1081 }
1082
1083 assert_eq!(vanguard_count(&vanguardmgr), new_params.l2_pool_size());
1084
1085 const MORE_VANGUARDS_PARAM: [(&str, i32); 1] = [("guard-hs-l2-number", 5)];
1087 let new_params = install_new_params(&rt, &netdir_provider, MORE_VANGUARDS_PARAM).await;
1092
1093 assert_eq!(vanguard_count(&vanguardmgr), new_params.l2_pool_size());
1095
1096 {
1097 let inner = vanguardmgr.inner.read().unwrap();
1098 let l2_count = inner.l2_vanguards().len();
1099 assert_eq!(l2_count, new_params.l2_pool_size());
1100 }
1101 });
1102 }
1103
1104 #[test]
1105 fn full_vanguards_persistence() {
1106 MockRuntime::test_with_various(|rt| async move {
1107 let vanguardmgr = VanguardMgr::new_testing(&rt, VanguardMode::Lite).unwrap();
1108
1109 let netdir =
1110 construct_custom_netdir_with_params(|_, _, _| {}, ENABLE_LITE_VANGUARDS, None)
1111 .unwrap()
1112 .unwrap_if_sufficient()
1113 .unwrap();
1114 let netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
1115
1116 assert_eq!(vanguardmgr.mode(), VanguardMode::Lite);
1119 assert!(vanguardmgr.storage.load().unwrap().is_none());
1120
1121 let mut rng = testing_rng();
1122 assert!(vanguardmgr
1123 .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
1124 .is_err());
1125
1126 switch_hs_mode_config(&vanguardmgr, VanguardMode::Full);
1130 rt.progress_until_stalled().await;
1131
1132 let vanguard_sets_orig = vanguardmgr.storage.load().unwrap();
1133 assert!(vanguardmgr
1134 .select_vanguard(&mut rng, &netdir, Layer3, &permissive_selector())
1135 .is_ok());
1136
1137 switch_hs_mode_config(&vanguardmgr, VanguardMode::Lite);
1139
1140 assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
1142 switch_hs_mode_config(&vanguardmgr, VanguardMode::Full);
1143 assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
1144
1145 switch_hs_mode_config(&vanguardmgr, VanguardMode::Lite);
1152
1153 let mut rng = testing_rng();
1154 let excluded_vanguard = vanguardmgr
1155 .select_vanguard(&mut rng, &netdir, Layer2, &permissive_selector())
1156 .unwrap();
1157
1158 let _ = install_netdir_excluding_vanguard(
1159 &rt,
1160 &excluded_vanguard,
1161 ENABLE_LITE_VANGUARDS,
1162 &netdir_provider,
1163 )
1164 .await;
1165
1166 assert_eq!(vanguard_sets_orig, vanguardmgr.storage.load().unwrap());
1168 let _ = install_netdir_excluding_vanguard(
1169 &rt,
1170 &excluded_vanguard,
1171 ENABLE_FULL_VANGUARDS,
1172 &netdir_provider,
1173 )
1174 .await;
1175 });
1176 }
1177
1178 #[test]
1179 fn load_from_state_file() {
1180 MockRuntime::test_with_various(|rt| async move {
1181 let now = time::UNIX_EPOCH + Duration::from_secs(1610000000);
1183 rt.jump_wallclock(now);
1184
1185 let config = VanguardConfig {
1186 mode: ExplicitOrAuto::Explicit(VanguardMode::Full),
1187 };
1188
1189 let (statemgr, _dir) =
1191 state_dir_with_vanguards(r#"{ "l2_vanguards": [], "l3_vanguards": [] }"#);
1192 let vanguardmgr = VanguardMgr::new(&config, rt.clone(), statemgr, false).unwrap();
1193 {
1194 let inner = vanguardmgr.inner.read().unwrap();
1195
1196 assert!(inner.vanguard_sets.l2().is_empty());
1198 assert!(inner.vanguard_sets.l3().is_empty());
1199 }
1200
1201 let (statemgr, _dir) = state_dir_with_vanguards(VANGUARDS_JSON);
1202 let vanguardmgr =
1203 Arc::new(VanguardMgr::new(&config, rt.clone(), statemgr, false).unwrap());
1204 let (initial_l2, initial_l3) = {
1205 let inner = vanguardmgr.inner.read().unwrap();
1206 let l2_vanguards = inner.vanguard_sets.l2_vanguards().clone();
1207 let l3_vanguards = inner.vanguard_sets.l3_vanguards().clone();
1208
1209 assert_eq!(l2_vanguards.len(), 3);
1212 assert_eq!(l3_vanguards.len(), 2);
1213 assert_eq!(inner.vanguard_sets.l2_vanguards_target(), 0);
1216 assert_eq!(inner.vanguard_sets.l3_vanguards_target(), 0);
1217 assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
1218 assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
1219
1220 (l2_vanguards, l3_vanguards)
1221 };
1222
1223 let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
1224 let _netdir_provider = vanguardmgr.init_vanguard_sets(&netdir).await.unwrap();
1225 {
1226 let inner = vanguardmgr.inner.read().unwrap();
1227 let l2_vanguards = inner.vanguard_sets.l2_vanguards();
1228 let l3_vanguards = inner.vanguard_sets.l3_vanguards();
1229
1230 assert_eq!(l2_vanguards.len(), 4);
1232 assert_eq!(l3_vanguards.len(), 8);
1233 assert_eq!(inner.vanguard_sets.l2_vanguards_target(), 4);
1235 assert_eq!(inner.vanguard_sets.l3_vanguards_target(), 8);
1236 assert_eq!(inner.vanguard_sets.l2_vanguards_deficit(), 0);
1237 assert_eq!(inner.vanguard_sets.l3_vanguards_deficit(), 0);
1238
1239 assert!(initial_l2.iter().all(|v| l2_vanguards.contains(v)));
1241 assert!(initial_l3.iter().all(|v| l3_vanguards.contains(v)));
1242 }
1243 });
1244 }
1245
1246 #[test]
1247 fn invalid_state_file() {
1248 MockRuntime::test_with_various(|rt| async move {
1249 let config = VanguardConfig {
1250 mode: ExplicitOrAuto::Explicit(VanguardMode::Full),
1251 };
1252 let (statemgr, _dir) = state_dir_with_vanguards(INVALID_VANGUARDS_JSON);
1253 let res = VanguardMgr::new(&config, rt.clone(), statemgr, false);
1254 assert!(matches!(res, Err(VanguardMgrError::State(_))));
1255 });
1256 }
1257}