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 async fn rotate_seeds_if_expiring(&self) -> Option<SystemTime> {
503 let mut expired_verifiers = vec![];
504 let mut new_verifiers = vec![];
505
506 let mut update_times = vec![];
507 let mut updated_tps = vec![];
508 let mut expired_tps = vec![];
509
510 let mut publisher_update_tx = {
511 let mut state = self.0.write().expect("Lock poisoned");
512
513 let config = state.config_rx.borrow().clone();
514 let keymgr = state.keymgr.clone();
515 let nickname = state.nickname.clone();
516
517 for (time_period, info) in state.seeds.iter_mut() {
518 let rotation_time = Self::calculate_early_rotation_time(info.next_expiration_time);
519 update_times.push(rotation_time);
520
521 if rotation_time <= SystemTime::get() {
522 let mut rng = rand::rng();
527
528 let seed = Seed::new(&mut rng, None);
529 let verifier = match Self::make_verifier(
530 &keymgr,
531 nickname.clone(),
532 *time_period,
533 seed.clone(),
534 &config,
535 ) {
536 Some(verifier) => verifier,
537 None => {
538 expired_tps.push(*time_period);
541 continue;
542 }
543 };
544
545 let expired_seed = if info.seeds.is_full() {
546 info.seeds.pop_at(0)
547 } else {
548 None
549 };
550 info.seeds.push(seed.clone());
552 info.next_expiration_time = Self::make_next_expiration_time(&mut rng);
553 update_times.push(info.next_expiration_time);
554
555 new_verifiers.push((seed, verifier));
557 if let Some(expired_seed) = expired_seed {
558 expired_verifiers.push(expired_seed.head());
559 }
560
561 updated_tps.push(*time_period);
563
564 tracing::debug!(time_period = ?time_period, "Rotated PoW seed");
565 }
566 }
567
568 for time_period in expired_tps {
569 if let Some(seeds) = state.seeds.remove(&time_period) {
570 for seed in seeds.seeds {
571 state.verifiers.remove(&seed.head());
572 }
573 }
574 }
575
576 for (seed, verifier) in new_verifiers {
577 let replay_log = Mutex::new(
578 PowNonceReplayLog::new_logged(&state.instance_dir, &seed)
579 .expect("Couldn't make ReplayLog."),
580 );
581 state.verifiers.insert(seed.head(), (verifier, replay_log));
582 }
583
584 for seed_head in expired_verifiers {
585 state.verifiers.remove(&seed_head);
586 }
587
588 let record = state.to_record();
589 if let Err(err) = state.storage_handle.store(&record) {
590 warn_report!(err, "Error saving PoW state");
591 }
592
593 state.publisher_update_tx.clone()
594 };
595
596 for time_period in updated_tps {
597 if let Err(err) = publisher_update_tx.send(time_period).await {
598 warn_report!(err, "Couldn't send update message to publisher");
599 }
600 }
601
602 update_times.iter().min().cloned()
603 }
604
605 pub(crate) fn get_pow_params<Rng: rand::Rng + CryptoRng>(
610 self: &Arc<Self>,
611 time_period: TimePeriod,
612 rng: &mut Rng,
613 ) -> Result<PowParams, PowError> {
614 let (seed_and_expiration, suggested_effort) = {
615 let state = self.0.read().expect("Lock poisoned");
616 let seed = state
617 .seeds
618 .get(&time_period)
619 .and_then(|x| Some((x.seeds.last()?.clone(), x.next_expiration_time)));
620 let suggested_effort = *state.suggested_effort.lock().expect("Lock poisoned");
621 (seed, suggested_effort)
622 };
623
624 let (seed, expiration) = match seed_and_expiration {
625 Some(seed) => seed,
626 None => {
627 let seed = Seed::new(rng, None);
630 let next_expiration_time = Self::make_next_expiration_time(rng);
631
632 let mut seeds = ArrayVec::new();
633 seeds.push(seed.clone());
634
635 let mut state = self.0.write().expect("Lock poisoned");
636
637 state.seeds.insert(
638 time_period,
639 SeedsForTimePeriod {
640 seeds,
641 next_expiration_time,
642 },
643 );
644
645 let verifier = Self::make_verifier(
646 &state.keymgr,
647 state.nickname.clone(),
648 time_period,
649 seed.clone(),
650 &state.config_rx.borrow(),
651 )
652 .ok_or(PowError::MissingKey)?;
653
654 let replay_log =
655 Mutex::new(PowNonceReplayLog::new_logged(&state.instance_dir, &seed)?);
656 state.verifiers.insert(seed.head(), (verifier, replay_log));
657
658 let record = state.to_record();
659 state.storage_handle.store(&record)?;
660
661 (seed, next_expiration_time)
662 }
663 };
664
665 Ok(PowParams::V1(PowParamsV1::new(
666 TimeRangeBound::new(seed, ..expiration),
667 suggested_effort,
668 )))
669 }
670
671 fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError> {
673 {
677 let state = self.0.write().expect("Lock poisoned");
678 let mut replay_log = match state.verifiers.get(&solve.seed_head()) {
679 Some((_, replay_log)) => replay_log.lock().expect("Lock poisoned"),
680 None => return Err(PowSolveError::InvalidSeedHead),
681 };
682 replay_log
683 .check_for_replay(solve.nonce())
684 .map_err(PowSolveError::NonceReplay)?;
685 }
686
687 let state = self.0.read().expect("Lock poisoned");
690 let verifier = match state.verifiers.get(&solve.seed_head()) {
691 Some((verifier, _)) => verifier,
692 None => return Err(PowSolveError::InvalidSeedHead),
693 };
694
695 let solution = match Solution::try_from_bytes(
696 solve.nonce().clone(),
697 solve.effort(),
698 solve.seed_head(),
699 solve.solution(),
700 ) {
701 Ok(solution) => solution,
702 Err(err) => return Err(PowSolveError::InvalidEquixSolution(err)),
703 };
704
705 match verifier.check(&solution) {
706 Ok(()) => Ok(()),
707 Err(err) => Err(PowSolveError::InvalidSolve(err)),
708 }
709 }
710}
711
712trait MockablePowManager {
714 fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError>;
716}
717
718impl<R: Runtime> MockablePowManager for PowManager<R> {
719 fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError> {
720 PowManager::check_solve(self, solve)
721 }
722}
723
724pub(crate) trait MockableRendRequest {
726 fn proof_of_work(&self) -> Result<Option<&ProofOfWork>, rend_handshake::IntroRequestError>;
728}
729
730impl MockableRendRequest for RendRequest {
731 fn proof_of_work(&self) -> Result<Option<&ProofOfWork>, rend_handshake::IntroRequestError> {
732 Ok(self
733 .intro_request()?
734 .intro_payload()
735 .proof_of_work_extension())
736 }
737}
738
739#[derive(Debug)]
741struct RendRequestOrdByEffort<Q> {
742 request: Q,
744 pow: Option<ProofOfWorkV1>,
746 max_effort: Effort,
749 recv_time: Instant,
751 request_num: u64,
758}
759
760impl<Q: MockableRendRequest> RendRequestOrdByEffort<Q> {
761 fn new(
763 request: Q,
764 max_effort: Effort,
765 request_num: u64,
766 ) -> Result<Self, rend_handshake::IntroRequestError> {
767 let pow = match request.proof_of_work()?.cloned() {
768 Some(ProofOfWork::V1(pow)) => Some(pow),
769 None | Some(_) => None,
770 };
771
772 Ok(Self {
773 request,
774 pow,
775 max_effort,
776 recv_time: Instant::get(),
777 request_num,
778 })
779 }
780}
781
782impl<Q: MockableRendRequest> Ord for RendRequestOrdByEffort<Q> {
783 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
784 let self_effort = self.pow.as_ref().map_or(Effort::zero(), |pow| {
785 Effort::min(pow.effort(), self.max_effort)
786 });
787 let other_effort = other.pow.as_ref().map_or(Effort::zero(), |pow| {
788 Effort::min(pow.effort(), other.max_effort)
789 });
790 match self_effort.cmp(&other_effort) {
791 std::cmp::Ordering::Equal => {
792 match other.recv_time.cmp(&self.recv_time) {
794 std::cmp::Ordering::Equal => other.request_num.cmp(&self.request_num),
797 not_equal => not_equal,
798 }
799 }
800 not_equal => not_equal,
801 }
802 }
803}
804
805impl<Q: MockableRendRequest> PartialOrd for RendRequestOrdByEffort<Q> {
806 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
807 Some(self.cmp(other))
808 }
809}
810
811impl<Q: MockableRendRequest> PartialEq for RendRequestOrdByEffort<Q> {
812 fn eq(&self, other: &Self) -> bool {
813 let self_effort = self.pow.as_ref().map_or(Effort::zero(), |pow| {
814 Effort::min(pow.effort(), self.max_effort)
815 });
816 let other_effort = other.pow.as_ref().map_or(Effort::zero(), |pow| {
817 Effort::min(pow.effort(), other.max_effort)
818 });
819 self_effort == other_effort && self.recv_time == other.recv_time
820 }
821}
822
823impl<Q: MockableRendRequest> Eq for RendRequestOrdByEffort<Q> {}
824
825pub(crate) struct RendRequestReceiver<R, Q>(Arc<Mutex<RendRequestReceiverInner<R, Q>>>);
837
838impl<R, Q> Clone for RendRequestReceiver<R, Q> {
839 fn clone(&self) -> Self {
840 Self(self.0.clone())
841 }
842}
843
844struct RendRequestReceiverInner<R, Q> {
846 queue: BTreeSet<RendRequestOrdByEffort<Q>>,
848
849 queue_pow_disabled: VecDeque<Q>,
854
855 waker: Option<Waker>,
857
858 runtime: R,
860
861 nickname: HsNickname,
863
864 netdir_provider: Arc<dyn NetDirProvider>,
866
867 config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
869
870 update_period_start: Instant,
872 num_enqueued_gte_suggested: usize,
875 num_dequeued: u32,
877 idle_time: Duration,
880 last_transition: Instant,
883 total_effort: u64,
885
886 suggested_effort: Arc<Mutex<Effort>>,
890
891 status_tx: PowManagerStatusSender,
893}
894
895impl<R: Runtime, Q: MockableRendRequest + Send + 'static> RendRequestReceiver<R, Q> {
896 fn new(
898 runtime: R,
899 nickname: HsNickname,
900 suggested_effort: Arc<Mutex<Effort>>,
901 netdir_provider: Arc<dyn NetDirProvider>,
902 status_tx: PowManagerStatusSender,
903 config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
904 ) -> Self {
905 let now = runtime.now();
906 RendRequestReceiver(Arc::new(Mutex::new(RendRequestReceiverInner {
907 queue: BTreeSet::new(),
908 queue_pow_disabled: VecDeque::new(),
909 waker: None,
910 runtime,
911 nickname,
912 netdir_provider,
913 config_rx,
914 update_period_start: now,
915 num_enqueued_gte_suggested: 0,
916 num_dequeued: 0,
917 idle_time: Duration::new(0, 0),
918 last_transition: now,
919 total_effort: 0,
920 suggested_effort,
921 status_tx,
922 })))
923 }
924
925 #[allow(clippy::let_underscore_future)]
928 fn start_accept_thread<P: MockablePowManager + Send + Sync + 'static>(
930 &self,
931 runtime: R,
932 pow_manager: Arc<P>,
933 inner_receiver: mpsc::Receiver<Q>,
934 ) {
935 let receiver = self.clone();
936 let runtime_clone = runtime.clone();
937 let _ = runtime.clone().spawn_blocking(move || {
938 if let Err(err) =
939 receiver
940 .clone()
941 .accept_loop(&runtime_clone, &pow_manager, inner_receiver)
942 {
943 warn_report!(err, "PoW accept loop error!");
944 receiver
945 .0
946 .lock()
947 .expect("Lock poisoned")
948 .status_tx
949 .send_broken(Problem::Pow(err));
950 }
951 });
952
953 let receiver = self.clone();
954 let _ = runtime.clone().spawn_blocking(move || {
955 if let Err(err) = receiver.clone().expire_old_requests_loop(&runtime) {
956 warn_report!(err, "PoW request expiration loop error!");
957 receiver
958 .0
959 .lock()
960 .expect("Lock poisoned")
961 .status_tx
962 .send_broken(Problem::Pow(err));
963 }
964 });
965 }
966
967 fn update_suggested_effort(&self, net_params: &NetParameters) {
969 let mut inner = self.0.lock().expect("Lock poisoned");
970
971 let decay_adjustment_fraction = net_params.hs_pow_v1_default_decay_adjustment.as_fraction();
972
973 let update_period_duration = inner.runtime.now() - inner.update_period_start;
974 if inner.num_dequeued != 0 && update_period_duration.as_millis() != 0 {
975 let avg_request_duration = update_period_duration / inner.num_dequeued;
976 let num_dequeued = f64::from(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 let suggested_effort_inner_f64 = f64::from(suggested_effort_inner);
995
996 if busy_fraction == 0.0 {
997 debug_assert!(decay_adjustment_fraction < 1.0);
1001 let new_suggested_effort =
1002 u32::from_f64(suggested_effort_inner_f64 * decay_adjustment_fraction)
1003 .expect("Conversion error");
1004 *suggested_effort = Effort::from(new_suggested_effort);
1005 } else {
1006 let theoretical_num_dequeued = num_dequeued * (1.0 / busy_fraction);
1011 let num_enqueued_gte_suggested_f64 = inner.num_enqueued_gte_suggested as f64;
1012
1013 if num_enqueued_gte_suggested_f64 >= theoretical_num_dequeued {
1014 let effort_per_dequeued = (inner.total_effort as f64 / num_dequeued) as u32;
1015 *suggested_effort = Effort::from(std::cmp::max(
1016 effort_per_dequeued,
1017 suggested_effort_inner + 1,
1018 ));
1019 } else {
1020 let decay = num_enqueued_gte_suggested_f64 / theoretical_num_dequeued;
1021 let adjusted_decay = decay + ((1.0 - decay) * decay_adjustment_fraction);
1022 let new_suggested_effort = (suggested_effort_inner_f64 * adjusted_decay) as u32;
1023 *suggested_effort = Effort::from(new_suggested_effort);
1024 }
1025 }
1026
1027 drop(suggested_effort);
1028 }
1029
1030 let now = inner.runtime.now();
1031
1032 inner.update_period_start = now;
1033 inner.num_enqueued_gte_suggested = 0;
1034 inner.num_dequeued = 0;
1035 inner.idle_time = Duration::new(0, 0);
1036 inner.last_transition = now;
1037 inner.total_effort = 0;
1038 }
1039
1040 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}