1use std::{
8 collections::{BTreeSet, HashMap, VecDeque},
9 sync::{Arc, Mutex, RwLock},
10 task::Waker,
11};
12
13use arrayvec::ArrayVec;
14use equix::EquiXBuilder;
15use futures::{SinkExt, StreamExt};
16use futures::{Stream, channel::mpsc};
17use num_traits::FromPrimitive;
18use rand::CryptoRng;
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21use tor_basic_utils::RngExt as _;
22use tor_cell::relaycell::hs::pow::{ProofOfWork, v1::ProofOfWorkV1};
23use tor_checkable::timed::TimerangeBound;
24use tor_error::warn_report;
25use tor_hscrypto::{
26 pk::HsBlindIdKey,
27 pow::v1::{
28 Effort, Instance, RuntimeOption, Seed, SeedHead, Solution, SolutionErrorV1, Verifier,
29 },
30 time::TimePeriod,
31};
32use tor_keymgr::KeyMgr;
33use tor_netdir::{NetDirProvider, NetdirProviderShutdown, params::NetParameters};
34use tor_netdoc::doc::hsdesc::pow::{PowParams, v1::PowParamsV1};
35use tor_persist::{
36 hsnickname::HsNickname,
37 state_dir::{InstanceRawSubdir, StorageHandle},
38};
39use tor_rtcompat::Runtime;
40use tor_rtcompat::SpawnExt;
41use web_time_compat::{Duration, Instant, InstantExt, SystemTime, SystemTimeExt};
42
43use crate::{
44 BlindIdPublicKeySpecifier, OnionServiceConfig, RendRequest, ReplayError, StartupError,
45 rend_handshake,
46 replay::{OpenReplayLogError, PowNonceReplayLog},
47 status::{PowManagerStatusSender, Problem, State as PowManagerState},
48};
49
50use super::NewPowManager;
51
52pub(crate) type PowManager<R> = PowManagerGeneric<R, RendRequest>;
54
55pub(crate) struct PowManagerGeneric<R, Q>(RwLock<State<R, Q>>);
57
58struct State<R, Q> {
60 seeds: HashMap<TimePeriod, SeedsForTimePeriod>,
65
66 verifiers: HashMap<SeedHead, (Verifier, Mutex<PowNonceReplayLog>)>,
68
69 nickname: HsNickname,
73
74 instance_dir: InstanceRawSubdir,
76
77 keymgr: Arc<KeyMgr>,
79
80 suggested_effort: Arc<Mutex<Effort>>,
84
85 runtime: R,
87
88 storage_handle: StorageHandle<PowManagerStateRecord>,
90
91 publisher_update_tx: mpsc::Sender<TimePeriod>,
94
95 rend_request_rx: RendRequestReceiver<R, Q>,
99
100 netdir_provider: Arc<dyn NetDirProvider>,
102
103 status_tx: PowManagerStatusSender,
105
106 config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
108}
109
110#[derive(Serialize, Deserialize, Debug, Clone)]
111struct SeedsForTimePeriod {
113 seeds: ArrayVec<Seed, 2>,
117
118 next_expiration_time: SystemTime,
120}
121
122#[derive(Debug)]
123#[allow(unused)]
124pub(crate) enum PowSolveError {
129 InvalidSeedHead,
131 NonceReplay(ReplayError),
133 InvalidEquixSolution(SolutionErrorV1),
135 InvalidSolve(tor_hscrypto::pow::Error),
137}
138
139#[derive(Serialize, Deserialize, Debug, Default)]
141pub(crate) struct PowManagerStateRecord {
142 seeds: Vec<(TimePeriod, SeedsForTimePeriod)>,
149
150 #[serde(default)]
152 suggested_effort: Effort,
153 }
161
162impl<R: Runtime, Q> State<R, Q> {
163 pub(crate) fn to_record(&self) -> PowManagerStateRecord {
165 PowManagerStateRecord {
166 seeds: self.seeds.clone().into_iter().collect(),
167 suggested_effort: *self.suggested_effort.lock().expect("Lock poisoned"),
168 }
169 }
170}
171
172const HS_UPDATE_PERIOD: Duration = Duration::from_secs(300);
174
175const SUGGESTED_EFFORT_DEADZONE: f64 = 0.15;
181
182const SEED_EARLY_ROTATION_TIME: Duration = Duration::from_secs(60 * 5);
184
185const EXPIRATION_TIME_MINS_MIN: u64 = 105;
188
189const EXPIRATION_TIME_MINS_MAX: u64 = 120;
192
193const _: () = assert!(
195 SEED_EARLY_ROTATION_TIME.as_secs() <= EXPIRATION_TIME_MINS_MIN * 60,
196 "Early rotation time must be less than minimum expiration time"
197);
198
199const _: () = assert!(
201 EXPIRATION_TIME_MINS_MIN <= EXPIRATION_TIME_MINS_MAX,
202 "Minimum expiration time must be less than or equal to max"
203);
204
205const PUBLISHER_UPDATE_QUEUE_DEPTH: usize = 32;
209
210#[derive(Error, Debug, Clone)]
211#[allow(dead_code)] #[non_exhaustive]
213pub enum PowError {
215 #[error("Missing required key.")]
217 MissingKey,
218 #[error(transparent)]
220 StorageError(#[from] tor_persist::Error),
221 #[error(transparent)]
223 OpenReplayLog(#[from] OpenReplayLogError),
224 #[error(transparent)]
226 NetdirProviderShutdown(#[from] NetdirProviderShutdown),
227}
228
229impl<R: Runtime, Q: MockableRendRequest + Send + 'static> PowManagerGeneric<R, Q> {
230 #[allow(clippy::new_ret_no_self, clippy::too_many_arguments)]
232 pub(crate) fn new(
233 runtime: R,
234 nickname: HsNickname,
235 instance_dir: InstanceRawSubdir,
236 keymgr: Arc<KeyMgr>,
237 storage_handle: StorageHandle<PowManagerStateRecord>,
238 netdir_provider: Arc<dyn NetDirProvider>,
239 status_tx: PowManagerStatusSender,
240 config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
241 ) -> Result<NewPowManager<R>, StartupError> {
242 let on_disk_state = storage_handle
243 .load()
244 .map_err(StartupError::LoadState)?
245 .unwrap_or(PowManagerStateRecord::default());
246
247 let seeds: HashMap<TimePeriod, SeedsForTimePeriod> =
248 on_disk_state.seeds.into_iter().collect();
249 let suggested_effort = Arc::new(Mutex::new(on_disk_state.suggested_effort));
250
251 let mut verifiers = HashMap::new();
252 for (tp, seeds_for_tp) in seeds.clone().into_iter() {
253 for seed in seeds_for_tp.seeds {
254 let verifier = match Self::make_verifier(
255 &keymgr,
256 nickname.clone(),
257 tp,
258 seed.clone(),
259 &config_rx.borrow(),
260 ) {
261 Some(verifier) => verifier,
262 None => {
263 tracing::warn!(
264 "Couldn't construct verifier (key not available?). We will continue without this key, but this may prevent clients from connecting..."
265 );
266 continue;
267 }
268 };
269 let replay_log = match PowNonceReplayLog::new_logged(&instance_dir, &seed) {
270 Ok(replay_log) => replay_log,
271 Err(err) => {
272 warn_report!(
273 err,
274 "Error constructing replay log. We will continue without the log, but be aware that this may allow attackers to bypass PoW defenses..."
275 );
276 continue;
277 }
278 };
279 verifiers.insert(seed.head(), (verifier, Mutex::new(replay_log)));
280 }
281 }
282
283 let (publisher_update_tx, publisher_update_rx) =
286 crate::mpsc_channel_no_memquota(PUBLISHER_UPDATE_QUEUE_DEPTH);
287
288 let (rend_req_tx, rend_req_rx_channel) = super::make_rend_queue();
289 let rend_req_rx = RendRequestReceiver::new(
290 runtime.clone(),
291 nickname.clone(),
292 suggested_effort.clone(),
293 netdir_provider.clone(),
294 status_tx.clone(),
295 config_rx.clone(),
296 );
297
298 let state = State {
299 seeds,
300 nickname,
301 instance_dir,
302 keymgr,
303 publisher_update_tx,
304 verifiers,
305 suggested_effort: suggested_effort.clone(),
306 runtime: runtime.clone(),
307 storage_handle,
308 rend_request_rx: rend_req_rx.clone(),
309 netdir_provider,
310 status_tx,
311 config_rx,
312 };
313 let pow_manager = Arc::new(PowManagerGeneric(RwLock::new(state)));
314
315 rend_req_rx.start_accept_thread(runtime, pow_manager.clone(), rend_req_rx_channel);
316
317 Ok(NewPowManager {
318 pow_manager,
319 rend_req_tx,
320 rend_req_rx: Box::pin(rend_req_rx),
321 publisher_update_rx,
322 })
323 }
324
325 pub(crate) fn launch(self: &Arc<Self>) -> Result<(), StartupError> {
327 let pow_manager = self.clone();
328 let runtime = pow_manager.0.read().expect("Lock poisoned").runtime.clone();
329
330 runtime
331 .spawn(pow_manager.main_loop_error_wrapper())
332 .map_err(|cause| StartupError::Spawn {
333 spawning: "pow manager",
334 cause: cause.into(),
335 })?;
336
337 self.0
338 .write()
339 .expect("Lock poisoned")
340 .status_tx
341 .send(PowManagerState::Running, None);
342 Ok(())
343 }
344
345 async fn main_loop_error_wrapper(self: Arc<Self>) {
347 if let Err(err) = self.clone().main_loop_task().await {
348 self.0
349 .write()
350 .expect("Lock poisoned")
351 .status_tx
352 .send_broken(Problem::Pow(err));
353 }
354 }
355
356 async fn main_loop_task(self: Arc<Self>) -> Result<(), PowError> {
358 let runtime = self.0.write().expect("Lock poisoned").runtime.clone();
359
360 let mut last_suggested_effort_update = runtime.now();
361 let mut last_published_suggested_effort: u32 = (*self
362 .0
363 .read()
364 .expect("Lock poisoned")
365 .suggested_effort
366 .lock()
367 .expect("Lock poisoned"))
368 .into();
369
370 let netdir_provider = self
371 .0
372 .read()
373 .expect("Lock poisoned")
374 .netdir_provider
375 .clone();
376 let net_params = netdir_provider
377 .wait_for_netdir(tor_netdir::Timeliness::Timely)
378 .await?
379 .params()
380 .clone();
381
382 loop {
383 let next_update_time = self.rotate_seeds_if_expiring().await;
384
385 if runtime.now() - last_suggested_effort_update >= HS_UPDATE_PERIOD {
387 let (tps_to_update, mut publisher_update_tx) = {
388 let mut tps_to_update = vec![];
389
390 let inner = self.0.read().expect("Lock poisoned");
391
392 inner.rend_request_rx.update_suggested_effort(&net_params);
393 last_suggested_effort_update = runtime.now();
394 let new_suggested_effort: u32 =
395 (*inner.suggested_effort.lock().expect("Lock poisoned")).into();
396
397 let percent_change =
398 f64::from(new_suggested_effort - last_published_suggested_effort)
399 / f64::from(last_published_suggested_effort);
400 if percent_change.abs() >= SUGGESTED_EFFORT_DEADZONE {
401 last_published_suggested_effort = new_suggested_effort;
402
403 tps_to_update = inner.seeds.iter().map(|x| *x.0).collect();
404 }
405
406 let publisher_update_tx = inner.publisher_update_tx.clone();
407 (tps_to_update, publisher_update_tx)
408 };
409
410 for time_period in tps_to_update {
411 let _ = publisher_update_tx.send(time_period).await;
412 }
413 }
414
415 let suggested_effort_update_delay = HS_UPDATE_PERIOD.saturating_sub(
416 runtime
417 .now()
418 .saturating_duration_since(last_suggested_effort_update),
419 );
420
421 const MAX_DELAY: Duration = Duration::from_secs(EXPIRATION_TIME_MINS_MIN * 60)
426 .checked_sub(SEED_EARLY_ROTATION_TIME)
427 .expect("SEED_EARLY_ROTATION_TIME too high, or EXPIRATION_TIME_MINS_MIN too low.");
428 let delay = next_update_time
429 .map(|x| x.duration_since(SystemTime::get()).unwrap_or(MAX_DELAY))
430 .unwrap_or(MAX_DELAY)
431 .min(MAX_DELAY)
432 .min(suggested_effort_update_delay);
433
434 tracing::debug!(next_wakeup = ?delay, "Recalculated PoW seeds.");
435
436 runtime.sleep(delay).await;
437 }
438 }
439
440 fn make_next_expiration_time<Rng: rand::Rng + CryptoRng>(rng: &mut Rng) -> SystemTime {
442 SystemTime::get()
443 + Duration::from_secs(
444 60 * rng
445 .gen_range_checked(EXPIRATION_TIME_MINS_MIN..=EXPIRATION_TIME_MINS_MAX)
446 .expect("Can't generate expiration_time"),
447 )
448 }
449
450 fn make_verifier(
457 keymgr: &Arc<KeyMgr>,
458 nickname: HsNickname,
459 time_period: TimePeriod,
460 seed: Seed,
461 config: &OnionServiceConfig,
462 ) -> Option<Verifier> {
463 let blind_id_spec = BlindIdPublicKeySpecifier::new(nickname, time_period);
464 let blind_id_key = match keymgr.get::<HsBlindIdKey>(&blind_id_spec) {
465 Ok(blind_id_key) => blind_id_key,
466 Err(err) => {
467 warn_report!(err, "KeyMgr error when getting blinded ID key for PoW");
468 None
469 }
470 };
471 let instance = Instance::new(blind_id_key?.id(), seed);
472 let mut equix = EquiXBuilder::default();
473 if *config.disable_pow_compilation() {
474 equix.runtime(RuntimeOption::InterpretOnly);
475 }
476 Some(Verifier::new_with_equix(instance, equix))
477 }
478
479 fn calculate_early_rotation_time(expiration_time: SystemTime) -> SystemTime {
482 expiration_time
493 .checked_sub(SEED_EARLY_ROTATION_TIME)
494 .expect("PoW seed expiration underflow")
495 }
496
497 #[allow(clippy::cognitive_complexity)]
503 async fn rotate_seeds_if_expiring(&self) -> Option<SystemTime> {
504 let mut expired_verifiers = vec![];
505 let mut new_verifiers = vec![];
506
507 let mut update_times = vec![];
508 let mut updated_tps = vec![];
509 let mut expired_tps = vec![];
510
511 let mut publisher_update_tx = {
512 let mut state = self.0.write().expect("Lock poisoned");
513
514 let config = state.config_rx.borrow().clone();
515 let keymgr = state.keymgr.clone();
516 let nickname = state.nickname.clone();
517
518 for (time_period, info) in state.seeds.iter_mut() {
519 let rotation_time = Self::calculate_early_rotation_time(info.next_expiration_time);
520 update_times.push(rotation_time);
521
522 if rotation_time <= SystemTime::get() {
523 let mut rng = rand::rng();
528
529 let seed = Seed::new(&mut rng, None);
530 let verifier = match Self::make_verifier(
531 &keymgr,
532 nickname.clone(),
533 *time_period,
534 seed.clone(),
535 &config,
536 ) {
537 Some(verifier) => verifier,
538 None => {
539 expired_tps.push(*time_period);
542 continue;
543 }
544 };
545
546 let expired_seed = if info.seeds.is_full() {
547 info.seeds.pop_at(0)
548 } else {
549 None
550 };
551 info.seeds.push(seed.clone());
553 info.next_expiration_time = Self::make_next_expiration_time(&mut rng);
554 update_times.push(info.next_expiration_time);
555
556 new_verifiers.push((seed, verifier));
558 if let Some(expired_seed) = expired_seed {
559 expired_verifiers.push(expired_seed.head());
560 }
561
562 updated_tps.push(*time_period);
564
565 tracing::debug!(time_period = ?time_period, "Rotated PoW seed");
566 }
567 }
568
569 for time_period in expired_tps {
570 if let Some(seeds) = state.seeds.remove(&time_period) {
571 for seed in seeds.seeds {
572 state.verifiers.remove(&seed.head());
573 }
574 }
575 }
576
577 for (seed, verifier) in new_verifiers {
578 let replay_log = Mutex::new(
579 PowNonceReplayLog::new_logged(&state.instance_dir, &seed)
580 .expect("Couldn't make ReplayLog."),
581 );
582 state.verifiers.insert(seed.head(), (verifier, replay_log));
583 }
584
585 for seed_head in expired_verifiers {
586 state.verifiers.remove(&seed_head);
587 }
588
589 let record = state.to_record();
590 if let Err(err) = state.storage_handle.store(&record) {
591 warn_report!(err, "Error saving PoW state");
592 }
593
594 state.publisher_update_tx.clone()
595 };
596
597 for time_period in updated_tps {
598 if let Err(err) = publisher_update_tx.send(time_period).await {
599 warn_report!(err, "Couldn't send update message to publisher");
600 }
601 }
602
603 update_times.iter().min().cloned()
604 }
605
606 pub(crate) fn get_pow_params<Rng: rand::Rng + CryptoRng>(
611 self: &Arc<Self>,
612 time_period: TimePeriod,
613 rng: &mut Rng,
614 ) -> Result<PowParams, PowError> {
615 let (seed_and_expiration, suggested_effort) = {
616 let state = self.0.read().expect("Lock poisoned");
617 let seed = state
618 .seeds
619 .get(&time_period)
620 .and_then(|x| Some((x.seeds.last()?.clone(), x.next_expiration_time)));
621 let suggested_effort = *state.suggested_effort.lock().expect("Lock poisoned");
622 (seed, suggested_effort)
623 };
624
625 let (seed, expiration) = match seed_and_expiration {
626 Some(seed) => seed,
627 None => {
628 let seed = Seed::new(rng, None);
631 let next_expiration_time = Self::make_next_expiration_time(rng);
632
633 let mut seeds = ArrayVec::new();
634 seeds.push(seed.clone());
635
636 let mut state = self.0.write().expect("Lock poisoned");
637
638 state.seeds.insert(
639 time_period,
640 SeedsForTimePeriod {
641 seeds,
642 next_expiration_time,
643 },
644 );
645
646 let verifier = Self::make_verifier(
647 &state.keymgr,
648 state.nickname.clone(),
649 time_period,
650 seed.clone(),
651 &state.config_rx.borrow(),
652 )
653 .ok_or(PowError::MissingKey)?;
654
655 let replay_log =
656 Mutex::new(PowNonceReplayLog::new_logged(&state.instance_dir, &seed)?);
657 state.verifiers.insert(seed.head(), (verifier, replay_log));
658
659 let record = state.to_record();
660 state.storage_handle.store(&record)?;
661
662 (seed, next_expiration_time)
663 }
664 };
665
666 Ok(PowParams::V1(PowParamsV1::new(
667 TimerangeBound::new(seed, ..expiration),
668 suggested_effort,
669 )))
670 }
671
672 fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError> {
674 {
678 let state = self.0.write().expect("Lock poisoned");
679 let mut replay_log = match state.verifiers.get(&solve.seed_head()) {
680 Some((_, replay_log)) => replay_log.lock().expect("Lock poisoned"),
681 None => return Err(PowSolveError::InvalidSeedHead),
682 };
683 replay_log
684 .check_for_replay(solve.nonce())
685 .map_err(PowSolveError::NonceReplay)?;
686 }
687
688 let state = self.0.read().expect("Lock poisoned");
691 let verifier = match state.verifiers.get(&solve.seed_head()) {
692 Some((verifier, _)) => verifier,
693 None => return Err(PowSolveError::InvalidSeedHead),
694 };
695
696 let solution = match Solution::try_from_bytes(
697 solve.nonce().clone(),
698 solve.effort(),
699 solve.seed_head(),
700 solve.solution(),
701 ) {
702 Ok(solution) => solution,
703 Err(err) => return Err(PowSolveError::InvalidEquixSolution(err)),
704 };
705
706 match verifier.check(&solution) {
707 Ok(()) => Ok(()),
708 Err(err) => Err(PowSolveError::InvalidSolve(err)),
709 }
710 }
711}
712
713trait MockablePowManager {
715 fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError>;
717}
718
719impl<R: Runtime> MockablePowManager for PowManager<R> {
720 fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError> {
721 PowManager::check_solve(self, solve)
722 }
723}
724
725pub(crate) trait MockableRendRequest {
727 fn proof_of_work(&self) -> Result<Option<&ProofOfWork>, rend_handshake::IntroRequestError>;
729}
730
731impl MockableRendRequest for RendRequest {
732 fn proof_of_work(&self) -> Result<Option<&ProofOfWork>, rend_handshake::IntroRequestError> {
733 Ok(self
734 .intro_request()?
735 .intro_payload()
736 .proof_of_work_extension())
737 }
738}
739
740#[derive(Debug)]
742struct RendRequestOrdByEffort<Q> {
743 request: Q,
745 pow: Option<ProofOfWorkV1>,
747 max_effort: Effort,
750 recv_time: Instant,
752 request_num: u64,
759}
760
761impl<Q: MockableRendRequest> RendRequestOrdByEffort<Q> {
762 fn new(
764 request: Q,
765 max_effort: Effort,
766 request_num: u64,
767 ) -> Result<Self, rend_handshake::IntroRequestError> {
768 let pow = match request.proof_of_work()?.cloned() {
769 Some(ProofOfWork::V1(pow)) => Some(pow),
770 None | Some(_) => None,
771 };
772
773 Ok(Self {
774 request,
775 pow,
776 max_effort,
777 recv_time: Instant::get(),
778 request_num,
779 })
780 }
781}
782
783impl<Q: MockableRendRequest> Ord for RendRequestOrdByEffort<Q> {
784 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
785 let self_effort = self.pow.as_ref().map_or(Effort::zero(), |pow| {
786 Effort::min(pow.effort(), self.max_effort)
787 });
788 let other_effort = other.pow.as_ref().map_or(Effort::zero(), |pow| {
789 Effort::min(pow.effort(), other.max_effort)
790 });
791 match self_effort.cmp(&other_effort) {
792 std::cmp::Ordering::Equal => {
793 match other.recv_time.cmp(&self.recv_time) {
795 std::cmp::Ordering::Equal => other.request_num.cmp(&self.request_num),
798 not_equal => not_equal,
799 }
800 }
801 not_equal => not_equal,
802 }
803 }
804}
805
806impl<Q: MockableRendRequest> PartialOrd for RendRequestOrdByEffort<Q> {
807 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
808 Some(self.cmp(other))
809 }
810}
811
812impl<Q: MockableRendRequest> PartialEq for RendRequestOrdByEffort<Q> {
813 fn eq(&self, other: &Self) -> bool {
814 let self_effort = self.pow.as_ref().map_or(Effort::zero(), |pow| {
815 Effort::min(pow.effort(), self.max_effort)
816 });
817 let other_effort = other.pow.as_ref().map_or(Effort::zero(), |pow| {
818 Effort::min(pow.effort(), other.max_effort)
819 });
820 self_effort == other_effort && self.recv_time == other.recv_time
821 }
822}
823
824impl<Q: MockableRendRequest> Eq for RendRequestOrdByEffort<Q> {}
825
826pub(crate) struct RendRequestReceiver<R, Q>(Arc<Mutex<RendRequestReceiverInner<R, Q>>>);
838
839impl<R, Q> Clone for RendRequestReceiver<R, Q> {
840 fn clone(&self) -> Self {
841 Self(self.0.clone())
842 }
843}
844
845struct RendRequestReceiverInner<R, Q> {
847 queue: BTreeSet<RendRequestOrdByEffort<Q>>,
849
850 queue_pow_disabled: VecDeque<Q>,
855
856 waker: Option<Waker>,
858
859 runtime: R,
861
862 nickname: HsNickname,
864
865 netdir_provider: Arc<dyn NetDirProvider>,
867
868 config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
870
871 update_period_start: Instant,
873 num_enqueued_gte_suggested: usize,
876 num_dequeued: u32,
878 idle_time: Duration,
881 last_transition: Instant,
884 total_effort: u64,
886
887 suggested_effort: Arc<Mutex<Effort>>,
891
892 status_tx: PowManagerStatusSender,
894}
895
896impl<R: Runtime, Q: MockableRendRequest + Send + 'static> RendRequestReceiver<R, Q> {
897 fn new(
899 runtime: R,
900 nickname: HsNickname,
901 suggested_effort: Arc<Mutex<Effort>>,
902 netdir_provider: Arc<dyn NetDirProvider>,
903 status_tx: PowManagerStatusSender,
904 config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
905 ) -> Self {
906 let now = runtime.now();
907 RendRequestReceiver(Arc::new(Mutex::new(RendRequestReceiverInner {
908 queue: BTreeSet::new(),
909 queue_pow_disabled: VecDeque::new(),
910 waker: None,
911 runtime,
912 nickname,
913 netdir_provider,
914 config_rx,
915 update_period_start: now,
916 num_enqueued_gte_suggested: 0,
917 num_dequeued: 0,
918 idle_time: Duration::new(0, 0),
919 last_transition: now,
920 total_effort: 0,
921 suggested_effort,
922 status_tx,
923 })))
924 }
925
926 #[allow(clippy::let_underscore_future)]
929 fn start_accept_thread<P: MockablePowManager + Send + Sync + 'static>(
931 &self,
932 runtime: R,
933 pow_manager: Arc<P>,
934 inner_receiver: mpsc::Receiver<Q>,
935 ) {
936 let receiver = self.clone();
937 let runtime_clone = runtime.clone();
938 let _ = runtime.clone().spawn_blocking(move || {
939 if let Err(err) =
940 receiver
941 .clone()
942 .accept_loop(&runtime_clone, &pow_manager, inner_receiver)
943 {
944 warn_report!(err, "PoW accept loop error!");
945 receiver
946 .0
947 .lock()
948 .expect("Lock poisoned")
949 .status_tx
950 .send_broken(Problem::Pow(err));
951 }
952 });
953
954 let receiver = self.clone();
955 let _ = runtime.clone().spawn_blocking(move || {
956 if let Err(err) = receiver.clone().expire_old_requests_loop(&runtime) {
957 warn_report!(err, "PoW request expiration loop error!");
958 receiver
959 .0
960 .lock()
961 .expect("Lock poisoned")
962 .status_tx
963 .send_broken(Problem::Pow(err));
964 }
965 });
966 }
967
968 fn update_suggested_effort(&self, net_params: &NetParameters) {
970 let mut inner = self.0.lock().expect("Lock poisoned");
971
972 let decay_adjustment_fraction = net_params.hs_pow_v1_default_decay_adjustment.as_fraction();
973
974 if inner.num_dequeued != 0 {
975 let update_period_duration = inner.runtime.now() - inner.update_period_start;
976 let avg_request_duration = update_period_duration / inner.num_dequeued;
977 if inner.queue.is_empty() {
978 let now = inner.runtime.now();
979 let last_transition = inner.last_transition;
980 inner.idle_time += now - last_transition;
981 }
982 let adjusted_idle_time = Duration::saturating_sub(
983 inner.idle_time,
984 avg_request_duration * inner.queue.len().try_into().expect("Queue too large."),
985 );
986 let idle_fraction = f64::from_u128(adjusted_idle_time.as_millis())
988 .expect("Conversion error")
989 / f64::from_u128(update_period_duration.as_millis()).expect("Conversion error");
990 let busy_fraction = 1.0 - idle_fraction;
991
992 let mut suggested_effort = inner.suggested_effort.lock().expect("Lock poisoned");
993 let suggested_effort_inner: u32 = (*suggested_effort).into();
994
995 if busy_fraction == 0.0 {
996 let new_suggested_effort =
997 u32::from_f64(f64::from(suggested_effort_inner) * decay_adjustment_fraction)
998 .expect("Conversion error");
999 *suggested_effort = Effort::from(new_suggested_effort);
1000 } else {
1001 let theoretical_num_dequeued =
1002 f64::from(inner.num_dequeued) * (1.0 / busy_fraction);
1003 let num_enqueued_gte_suggested_f64 =
1004 f64::from_usize(inner.num_enqueued_gte_suggested).expect("Conversion error");
1005
1006 if num_enqueued_gte_suggested_f64 >= theoretical_num_dequeued {
1007 let effort_per_dequeued = u32::from_f64(
1008 f64::from_u64(inner.total_effort).expect("Conversion error")
1009 / f64::from(inner.num_dequeued),
1010 )
1011 .expect("Conversion error");
1012 *suggested_effort = Effort::from(std::cmp::max(
1013 effort_per_dequeued,
1014 suggested_effort_inner + 1,
1015 ));
1016 } else {
1017 let decay = num_enqueued_gte_suggested_f64 / theoretical_num_dequeued;
1018 let adjusted_decay = decay + ((1.0 - decay) * decay_adjustment_fraction);
1019 let new_suggested_effort =
1020 u32::from_f64(f64::from(suggested_effort_inner) * adjusted_decay)
1021 .expect("Conversion error");
1022 *suggested_effort = Effort::from(new_suggested_effort);
1023 }
1024 }
1025
1026 drop(suggested_effort);
1027 }
1028
1029 let now = inner.runtime.now();
1030
1031 inner.update_period_start = now;
1032 inner.num_enqueued_gte_suggested = 0;
1033 inner.num_dequeued = 0;
1034 inner.idle_time = Duration::new(0, 0);
1035 inner.last_transition = now;
1036 inner.total_effort = 0;
1037 }
1038
1039 #[allow(clippy::cognitive_complexity)]
1042 fn accept_loop<P: MockablePowManager>(
1043 self,
1044 runtime: &R,
1045 pow_manager: &Arc<P>,
1046 mut receiver: mpsc::Receiver<Q>,
1047 ) -> Result<(), PowError> {
1048 let mut request_num = 0;
1049
1050 let netdir_provider = self
1051 .0
1052 .lock()
1053 .expect("Lock poisoned")
1054 .netdir_provider
1055 .clone();
1056 let net_params = runtime
1057 .reenter_block_on(netdir_provider.wait_for_netdir(tor_netdir::Timeliness::Timely))?
1058 .params()
1059 .clone();
1060
1061 let max_effort: u32 = net_params
1062 .hs_pow_v1_max_effort
1063 .get()
1064 .try_into()
1065 .expect("Bounded i32 not in range of u32?!");
1066 let max_effort = Effort::from(max_effort);
1067
1068 let config_rx = self.0.lock().expect("Lock poisoned").config_rx.clone();
1069
1070 let nickname = self.0.lock().expect("Lock poisoned").nickname.to_string();
1071
1072 cfg_if::cfg_if! {
1073 if #[cfg(feature = "metrics")] {
1074 let counter_rendrequest_error_total = metrics::counter!(
1075 description: "Number of errors processing rendezvous requests in the PoW subsystem.",
1076 unit: metrics::Unit::Count,
1077 "arti_hss_pow_rendrequest_error_total",
1078 "nickname" => nickname.clone()
1079 );
1080 let counter_rendrequest_verification_failure = metrics::counter!(
1081 description: "Number of PoW verification failures.",
1082 unit: metrics::Unit::Count,
1083 "arti_hss_pow_rendrequest_verification_failure_total",
1084 "nickname" => nickname.clone()
1085 );
1086 let counter_rend_queue_overflow = metrics::counter!(
1087 description: "Number of times the PoW rendezvous request queue overflowed, leading to dropped requests.",
1088 unit: metrics::Unit::Count,
1089 "arti_hss_pow_rend_queue_overflow_total",
1090 "nickname" => nickname.clone()
1091 );
1092 let counter_rendrequest_enqueued = metrics::counter!(
1093 description: "Number of rendezvous requests enqueued in the PoW subsystem.",
1094 unit: metrics::Unit::Count,
1095 "arti_hss_pow_rendrequest_enqueued_total",
1096 "nickname" => nickname.clone()
1097 );
1098 let histogram_rendrequest_effort = metrics::histogram!(
1099 description: "Histogram of effort values seen for incoming PoW requests.",
1100 "arti_hss_pow_rendrequest_effort_hist",
1101 "nickname" => nickname.clone()
1102 );
1103 }
1104 }
1105
1106 loop {
1107 let rend_request = if let Some(rend_request) = runtime.reenter_block_on(receiver.next())
1108 {
1109 rend_request
1110 } else {
1111 self.0
1112 .lock()
1113 .expect("Lock poisoned")
1114 .status_tx
1115 .send_shutdown();
1116 return Ok(());
1117 };
1118
1119 if config_rx.borrow().enable_pow {
1120 let rend_request =
1121 match RendRequestOrdByEffort::new(rend_request, max_effort, request_num) {
1122 Ok(rend_request) => rend_request,
1123 Err(err) => {
1124 #[cfg(feature = "metrics")]
1125 counter_rendrequest_error_total.increment(1);
1126 tracing::trace!(?err, "Error processing RendRequest");
1127 continue;
1128 }
1129 };
1130
1131 request_num = request_num.wrapping_add(1);
1132
1133 if let Some(ref pow) = rend_request.pow {
1134 if let Err(err) = pow_manager.check_solve(pow) {
1135 tracing::debug!(?err, "PoW verification failed");
1136 #[cfg(feature = "metrics")]
1137 counter_rendrequest_verification_failure.increment(1);
1138 continue;
1139 } else {
1140 #[cfg(feature = "metrics")]
1141 {
1142 let effort: u32 = pow.effort().into();
1143 histogram_rendrequest_effort.record(effort);
1144 }
1145 }
1146 }
1147
1148 let mut inner = self.0.lock().expect("Lock poisoned");
1149 if inner.queue.is_empty() {
1150 let now = runtime.now();
1151 let last_transition = inner.last_transition;
1152 inner.idle_time += now - last_transition;
1153 inner.last_transition = now;
1154 }
1155 if let Some(ref request_pow) = rend_request.pow {
1156 if request_pow.effort()
1157 >= *inner.suggested_effort.lock().expect("Lock poisoned")
1158 {
1159 inner.num_enqueued_gte_suggested += 1;
1160 let effort: u32 = request_pow.effort().into();
1161 if let Some(total_effort) = inner.total_effort.checked_add(effort.into()) {
1162 inner.total_effort = total_effort;
1163 } else {
1164 tracing::warn!(
1165 "PoW total_effort would overflow. The total effort has been capped, but this is not expected to happen - please file a bug report with logs and information about the circumstances under which this occurred."
1166 );
1167 inner.total_effort = u64::MAX;
1168 }
1169 }
1170 }
1171 if inner.queue.len() >= config_rx.borrow().pow_rend_queue_depth {
1172 let dropped_request = inner.queue.pop_first();
1173 #[cfg(feature = "metrics")]
1174 counter_rend_queue_overflow.increment(1);
1175 tracing::debug!(
1176 dropped_effort = ?dropped_request.map(|x| x.pow.map(|x| x.effort())),
1177 "RendRequest queue full, dropping request."
1178 );
1179 }
1180 inner.queue.insert(rend_request);
1181 #[cfg(feature = "metrics")]
1182 counter_rendrequest_enqueued.increment(1);
1183 if let Some(waker) = &inner.waker {
1184 waker.wake_by_ref();
1185 }
1186 } else {
1187 let mut inner = self.0.lock().expect("Lock poisoned");
1191 inner.queue_pow_disabled.push_back(rend_request);
1192 #[cfg(feature = "metrics")]
1193 counter_rendrequest_enqueued.increment(1);
1194 if let Some(waker) = &inner.waker {
1195 waker.wake_by_ref();
1196 }
1197 }
1198 }
1199 }
1200
1201 fn expire_old_requests_loop(self, runtime: &R) -> Result<(), PowError> {
1203 let netdir_provider = self
1204 .0
1205 .lock()
1206 .expect("Lock poisoned")
1207 .netdir_provider
1208 .clone();
1209 let net_params = runtime
1210 .reenter_block_on(netdir_provider.wait_for_netdir(tor_netdir::Timeliness::Timely))?
1211 .params()
1212 .clone();
1213
1214 let max_age: Duration = net_params
1215 .hs_pow_v1_service_intro_timeout
1216 .try_into()
1217 .expect(
1218 "Couldn't convert HiddenServiceProofOfWorkV1ServiceIntroTimeoutSeconds to Duration",
1219 );
1220
1221 let nickname = self.0.lock().expect("Lock poisoned").nickname.to_string();
1222 #[cfg(feature = "metrics")]
1223 let counter_rendrequest_expired = metrics::counter!("arti_hss_pow_rendrequest_expired_total", "nickname" => nickname.clone());
1224
1225 loop {
1226 let inner = self.0.lock().expect("Lock poisoned");
1227 let wait_time = inner
1230 .queue
1231 .first()
1232 .map(|r| {
1233 max_age.saturating_sub(runtime.now().saturating_duration_since(r.recv_time))
1234 })
1235 .unwrap_or(max_age);
1236 drop(inner);
1237
1238 runtime.reenter_block_on(runtime.sleep(wait_time));
1239
1240 let mut inner = self.0.lock().expect("Lock poisoned");
1241 let now = runtime.now();
1242 let prev_len = inner.queue.len();
1243 inner.queue.retain(|r| now - r.recv_time < max_age);
1244 let dropped = prev_len - inner.queue.len();
1245 tracing::trace!(dropped, "Expired timed out RendRequests");
1246 #[cfg(feature = "metrics")]
1247 counter_rendrequest_expired
1248 .increment(dropped.try_into().expect("usize overflowed u64!"));
1249 }
1250 }
1251}
1252
1253impl<R: Runtime, Q: MockableRendRequest> Stream for RendRequestReceiver<R, Q> {
1254 type Item = Q;
1255
1256 fn poll_next(
1257 self: std::pin::Pin<&mut Self>,
1258 cx: &mut std::task::Context<'_>,
1259 ) -> std::task::Poll<Option<Self::Item>> {
1260 let mut inner = self.get_mut().0.lock().expect("Lock poisoned");
1261 if inner.config_rx.borrow().enable_pow {
1262 match inner.queue.pop_last() {
1263 Some(item) => {
1264 inner.num_dequeued += 1;
1265 if inner.queue.is_empty() {
1266 inner.last_transition = inner.runtime.now();
1267 }
1268 std::task::Poll::Ready(Some(item.request))
1269 }
1270 None => {
1271 inner.waker = Some(cx.waker().clone());
1272 std::task::Poll::Pending
1273 }
1274 }
1275 } else if let Some(request) = inner.queue_pow_disabled.pop_front() {
1276 std::task::Poll::Ready(Some(request))
1279 } else {
1280 inner.waker = Some(cx.waker().clone());
1281 std::task::Poll::Pending
1282 }
1283 }
1284}
1285
1286#[cfg(test)]
1287mod test {
1288 #![allow(clippy::unwrap_used)]
1289 use crate::config::OnionServiceConfigBuilder;
1290 use crate::status::{OnionServiceStatus, StatusSender};
1291
1292 use super::*;
1293 use futures::FutureExt;
1294 use tor_hscrypto::pow::v1::{Nonce, SolutionByteArray};
1295 use tor_netdir::{testnet, testprovider::TestNetDirProvider};
1296 use tor_rtmock::MockRuntime;
1297
1298 struct MockPowManager;
1299
1300 #[derive(Debug)]
1301 struct MockRendRequest {
1302 id: usize,
1303 pow: Option<ProofOfWork>,
1304 }
1305
1306 impl MockablePowManager for MockPowManager {
1307 fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError> {
1308 if solve.solution() == &[0; 16] {
1310 Ok(())
1311 } else {
1312 Err(PowSolveError::InvalidSeedHead)
1313 }
1314 }
1315 }
1316
1317 impl MockableRendRequest for MockRendRequest {
1318 fn proof_of_work(&self) -> Result<Option<&ProofOfWork>, rend_handshake::IntroRequestError> {
1319 Ok(self.pow.as_ref())
1320 }
1321 }
1322
1323 fn make_req(id: usize, effort: Option<u32>) -> MockRendRequest {
1324 MockRendRequest {
1325 id,
1326 pow: effort.map(|e| {
1327 ProofOfWork::V1(ProofOfWorkV1::new(
1328 Nonce::from([0; 16]),
1329 Effort::from(e),
1330 SeedHead::from([0; 4]),
1331 SolutionByteArray::from([0; 16]),
1332 ))
1333 }),
1334 }
1335 }
1336
1337 fn make_req_invalid(id: usize, effort: u32) -> MockRendRequest {
1338 MockRendRequest {
1339 id,
1340 pow: Some(ProofOfWork::V1(ProofOfWorkV1::new(
1341 Nonce::from([0; 16]),
1342 Effort::from(effort),
1343 SeedHead::from([0; 4]),
1344 SolutionByteArray::from([1; 16]),
1345 ))),
1346 }
1347 }
1348
1349 #[allow(clippy::type_complexity)]
1350 fn make_test_receiver(
1351 runtime: &MockRuntime,
1352 netdir_params: Vec<(String, i32)>,
1353 config: Option<OnionServiceConfig>,
1354 ) -> (
1355 RendRequestReceiver<MockRuntime, MockRendRequest>,
1356 mpsc::Sender<MockRendRequest>,
1357 Arc<Mutex<Effort>>,
1358 NetParameters,
1359 postage::watch::Sender<Arc<OnionServiceConfig>>,
1360 ) {
1361 let pow_manager = Arc::new(MockPowManager);
1362 let suggested_effort = Arc::new(Mutex::new(Effort::zero()));
1363 let netdir = testnet::construct_custom_netdir_with_params(
1364 testnet::simple_net_func,
1365 netdir_params,
1366 None,
1367 )
1368 .unwrap()
1369 .unwrap_if_sufficient()
1370 .unwrap();
1371 let net_params = netdir.params().clone();
1372 let netdir_provider: Arc<TestNetDirProvider> = Arc::new(netdir.into());
1373 let status_tx = StatusSender::new(OnionServiceStatus::new_shutdown()).into();
1374 let nickname = HsNickname::new("test-hs".to_string()).unwrap();
1375 let (config_tx, config_rx) = postage::watch::channel_with(Arc::new(
1376 config.unwrap_or(
1377 OnionServiceConfigBuilder::default()
1378 .nickname(nickname.clone())
1379 .enable_pow(true)
1380 .build()
1381 .unwrap(),
1382 ),
1383 ));
1384 let receiver: RendRequestReceiver<_, MockRendRequest> = RendRequestReceiver::new(
1385 runtime.clone(),
1386 nickname.clone(),
1387 suggested_effort.clone(),
1388 netdir_provider,
1389 status_tx,
1390 config_rx,
1391 );
1392 let (tx, rx) = mpsc::channel(32);
1393 receiver.start_accept_thread(runtime.clone(), pow_manager, rx);
1394
1395 (receiver, tx, suggested_effort, net_params, config_tx)
1396 }
1397
1398 #[test]
1399 fn test_basic_pow_ordering() {
1400 MockRuntime::test_with_various(|runtime| async move {
1401 let (mut receiver, mut tx, _suggested_effort, _net_params, _config_tx) =
1402 make_test_receiver(&runtime, vec![], None);
1403
1404 tx.send(make_req(0, None)).await.unwrap();
1406 assert_eq!(receiver.next().await.unwrap().id, 0);
1407
1408 tx.send(make_req(1, Some(0))).await.unwrap();
1410 assert_eq!(receiver.next().await.unwrap().id, 1);
1411
1412 tx.send(make_req(2, Some(0))).await.unwrap();
1414 tx.send(make_req(3, Some(16))).await.unwrap();
1415 runtime.progress_until_stalled().await;
1416 assert_eq!(receiver.next().await.unwrap().id, 3);
1417 assert_eq!(receiver.next().await.unwrap().id, 2);
1418
1419 tx.send(make_req_invalid(4, 32)).await.unwrap();
1421 tx.send(make_req(5, Some(16))).await.unwrap();
1422 runtime.progress_until_stalled().await;
1423 assert_eq!(receiver.next().await.unwrap().id, 5);
1424 assert_eq!(receiver.0.lock().unwrap().queue.len(), 0);
1425 });
1426 }
1427
1428 #[test]
1429 fn test_suggested_effort_increase() {
1430 MockRuntime::test_with_various(|runtime| async move {
1431 let (mut receiver, mut tx, suggested_effort, net_params, _config_tx) =
1432 make_test_receiver(
1433 &runtime,
1434 vec![(
1435 "HiddenServiceProofOfWorkV1ServiceIntroTimeoutSeconds".to_string(),
1436 60000,
1437 )],
1438 None,
1439 );
1440
1441 for n in 0..128 {
1444 tx.send(make_req(n, Some(0))).await.unwrap();
1445 }
1446
1447 runtime.advance_by(HS_UPDATE_PERIOD / 2).await;
1448
1449 for _ in 0..128 {
1450 receiver.next().await.unwrap();
1451 }
1452
1453 runtime.advance_by(HS_UPDATE_PERIOD / 2).await;
1454 receiver.update_suggested_effort(&net_params);
1455
1456 assert_eq!(suggested_effort.lock().unwrap().clone(), Effort::zero());
1457
1458 for n in 0..128 {
1462 tx.send(make_req(n, Some(0))).await.unwrap();
1463 }
1464
1465 runtime.advance_by(HS_UPDATE_PERIOD / 2).await;
1466
1467 for _ in 0..64 {
1468 receiver.next().await.unwrap();
1469 }
1470
1471 runtime.advance_by(HS_UPDATE_PERIOD / 2).await;
1472 receiver.update_suggested_effort(&net_params);
1473
1474 let mut new_suggested_effort = *suggested_effort.lock().unwrap();
1475 assert!(new_suggested_effort > Effort::zero());
1476
1477 for n in 0..64 {
1480 tx.send(make_req(n, Some(new_suggested_effort.into())))
1481 .await
1482 .unwrap();
1483 }
1484
1485 receiver.next().await.unwrap();
1486 runtime.advance_by(HS_UPDATE_PERIOD).await;
1487 receiver.update_suggested_effort(&net_params);
1488
1489 let mut old_suggested_effort = new_suggested_effort;
1490 new_suggested_effort = *suggested_effort.lock().unwrap();
1491 assert!(new_suggested_effort > old_suggested_effort);
1492
1493 for n in 0..32 {
1496 tx.send(make_req(n, Some(new_suggested_effort.into())))
1497 .await
1498 .unwrap();
1499 }
1500
1501 runtime.advance_by(HS_UPDATE_PERIOD / 16 * 15).await;
1502
1503 while receiver.next().now_or_never().is_some() {
1504 }
1506
1507 runtime.advance_by(HS_UPDATE_PERIOD / 16).await;
1508 receiver.update_suggested_effort(&net_params);
1509
1510 old_suggested_effort = new_suggested_effort;
1511 new_suggested_effort = *suggested_effort.lock().unwrap();
1512 assert!(new_suggested_effort < old_suggested_effort);
1513 assert!(new_suggested_effort > Effort::zero());
1514
1515 let mut num_loops = 0;
1518 loop {
1519 tx.send(make_req(0, Some(new_suggested_effort.into())))
1520 .await
1521 .unwrap();
1522 runtime.advance_by(HS_UPDATE_PERIOD / 2).await;
1523
1524 while receiver.next().now_or_never().is_some() {
1525 }
1527
1528 runtime.advance_by(HS_UPDATE_PERIOD / 2).await;
1529 receiver.update_suggested_effort(&net_params);
1530
1531 old_suggested_effort = new_suggested_effort;
1532 new_suggested_effort = *suggested_effort.lock().unwrap();
1533
1534 assert!(new_suggested_effort < old_suggested_effort);
1535
1536 if new_suggested_effort == Effort::zero() {
1537 break;
1538 }
1539
1540 num_loops += 1;
1541
1542 if num_loops > 5 {
1543 panic!("Took too long for suggested effort to fall!");
1544 }
1545 }
1546 });
1547 }
1548
1549 #[test]
1550 fn test_rendrequest_timeout() {
1551 MockRuntime::test_with_various(|runtime| async move {
1552 let (receiver, mut tx, _suggested_effort, net_params, _config_tx) =
1553 make_test_receiver(&runtime, vec![], None);
1554
1555 let r0 = MockRendRequest { id: 0, pow: None };
1556 tx.send(r0).await.unwrap();
1557
1558 let max_age: Duration = net_params
1559 .hs_pow_v1_service_intro_timeout
1560 .try_into()
1561 .unwrap();
1562 runtime.advance_by(max_age * 2).await;
1563
1564 assert_eq!(receiver.0.lock().unwrap().queue.len(), 0);
1566 });
1567 }
1568
1569 #[test]
1570 fn test_pow_disabled() {
1571 MockRuntime::test_with_various(|runtime| async move {
1572 let (mut receiver, mut tx, _suggested_effort, _net_params, _config_tx) =
1573 make_test_receiver(
1574 &runtime,
1575 vec![],
1576 Some(
1577 OnionServiceConfigBuilder::default()
1578 .nickname(HsNickname::new("test-hs".to_string()).unwrap())
1579 .enable_pow(false)
1580 .build()
1581 .unwrap(),
1582 ),
1583 );
1584
1585 tx.send(make_req(0, None)).await.unwrap();
1587 tx.send(make_req(1, Some(0))).await.unwrap();
1588 tx.send(make_req(2, Some(20))).await.unwrap();
1589 tx.send(make_req(3, Some(10))).await.unwrap();
1590
1591 runtime.progress_until_stalled().await;
1592
1593 assert_eq!(receiver.next().await.unwrap().id, 0);
1595 assert_eq!(receiver.next().await.unwrap().id, 1);
1596 assert_eq!(receiver.next().await.unwrap().id, 2);
1597 assert_eq!(receiver.next().await.unwrap().id, 3);
1598 });
1599 }
1600
1601 #[test]
1602 fn test_rend_queue_max_depth() {
1603 MockRuntime::test_with_various(|runtime| async move {
1604 let (mut receiver, mut tx, _suggested_effort, _net_params, mut config_tx) =
1605 make_test_receiver(
1606 &runtime,
1607 vec![],
1608 Some(
1609 OnionServiceConfigBuilder::default()
1610 .nickname(HsNickname::new("test-hs".to_string()).unwrap())
1611 .enable_pow(true)
1612 .pow_rend_queue_depth(2)
1613 .build()
1614 .unwrap(),
1615 ),
1616 );
1617
1618 tx.send(make_req(0, None)).await.unwrap();
1619 tx.send(make_req(1, None)).await.unwrap();
1620 tx.send(make_req(2, None)).await.unwrap();
1621
1622 runtime.progress_until_stalled().await;
1623
1624 assert!(receiver.next().await.is_some());
1625 assert!(receiver.next().await.is_some());
1626 assert_eq!(receiver.0.lock().unwrap().queue.len(), 0);
1627
1628 config_tx
1631 .send(Arc::new(
1632 OnionServiceConfigBuilder::default()
1633 .nickname(HsNickname::new("test-hs".to_string()).unwrap())
1634 .enable_pow(true)
1635 .pow_rend_queue_depth(8)
1636 .build()
1637 .unwrap(),
1638 ))
1639 .await
1640 .unwrap();
1641
1642 tx.send(make_req(0, None)).await.unwrap();
1643 tx.send(make_req(1, None)).await.unwrap();
1644 tx.send(make_req(2, None)).await.unwrap();
1645
1646 runtime.progress_until_stalled().await;
1647
1648 assert!(receiver.next().await.is_some());
1649 assert!(receiver.next().await.is_some());
1650 assert!(receiver.next().await.is_some());
1651 });
1652 }
1653}