Skip to main content

tor_hsservice/pow/
v1.rs

1//! Code implementing version 1 proof-of-work for onion service hosts.
2//!
3//! Spec links:
4//! * <https://spec.torproject.org/hspow-spec/common-protocol.html>
5//! * <https://spec.torproject.org/hspow-spec/v1-equix.html>
6
7use 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
52/// Proof-of-Work manager type alias for production, using concrete [`RendRequest`].
53pub(crate) type PowManager<R> = PowManagerGeneric<R, RendRequest>;
54
55/// This is responsible for rotating Proof-of-Work seeds and doing verification of PoW solves.
56pub(crate) struct PowManagerGeneric<R, Q>(RwLock<State<R, Q>>);
57
58/// Internal state for [`PowManagerGeneric`].
59struct State<R, Q> {
60    /// The [`Seed`]s for a given [`TimePeriod`]
61    ///
62    /// The [`ArrayVec`] contains the current and previous seed, and the [`SystemTime`] is when the
63    /// current seed will expire.
64    seeds: HashMap<TimePeriod, SeedsForTimePeriod>,
65
66    /// Verifiers for all the seeds that exist in `seeds`.
67    verifiers: HashMap<SeedHead, (Verifier, Mutex<PowNonceReplayLog>)>,
68
69    /// The nickname for this hidden service.
70    ///
71    /// We need this so we can get the blinded keys from the [`KeyMgr`].
72    nickname: HsNickname,
73
74    /// Directory used to store nonce replay log.
75    instance_dir: InstanceRawSubdir,
76
77    /// Key manager.
78    keymgr: Arc<KeyMgr>,
79
80    /// Current suggested effort that we publish in the pow-params line.
81    ///
82    /// This is only read by the PowManagerGeneric, and is written to by the [`RendRequestReceiver`].
83    suggested_effort: Arc<Mutex<Effort>>,
84
85    /// Runtime
86    runtime: R,
87
88    /// Handle for storing state we need to persist to disk.
89    storage_handle: StorageHandle<PowManagerStateRecord>,
90
91    /// Queue to tell the publisher to re-upload a descriptor for a given TP, since we've rotated
92    /// that seed.
93    publisher_update_tx: mpsc::Sender<TimePeriod>,
94
95    /// The [`RendRequestReceiver`], which contains the queue of [`RendRequest`]s.
96    ///
97    /// We need a reference to this in order to tell it when to update the suggested_effort value.
98    rend_request_rx: RendRequestReceiver<R, Q>,
99
100    /// [`NetDirProvider`], used for getting consensus parameters for configuration values.
101    netdir_provider: Arc<dyn NetDirProvider>,
102
103    /// Sender for reporting back onion service status.
104    status_tx: PowManagerStatusSender,
105
106    /// Receiver for the current configuration.
107    config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
108}
109
110#[derive(Serialize, Deserialize, Debug, Clone)]
111/// Information about the current and previous [`Seed`] for a given [`TimePeriod`].
112struct SeedsForTimePeriod {
113    /// The previous and current [`Seed`].
114    ///
115    /// The last element in this array is the current seed.
116    seeds: ArrayVec<Seed, 2>,
117
118    /// When the current seed will expire.
119    next_expiration_time: SystemTime,
120}
121
122#[derive(Debug)]
123#[allow(unused)]
124/// A PoW solve was invalid.
125///
126/// While this contains the reason for the failure, we probably just want to use that for
127/// debugging, we shouldn't make any logical decisions based on what the particular error was.
128pub(crate) enum PowSolveError {
129    /// Seed head was not recognized, it may be expired.
130    InvalidSeedHead,
131    /// We have already seen a solve with this nonce
132    NonceReplay(ReplayError),
133    /// The bytes given as a solution do not form a valid Equi-X puzzle
134    InvalidEquixSolution(SolutionErrorV1),
135    /// The solution given was invalid.
136    InvalidSolve(tor_hscrypto::pow::Error),
137}
138
139/// On-disk record of [`PowManagerGeneric`] state.
140#[derive(Serialize, Deserialize, Debug, Default)]
141pub(crate) struct PowManagerStateRecord {
142    /// Seeds for each time period.
143    ///
144    /// Conceptually, this is a map between TimePeriod and SeedsForTimePeriod, but since TimePeriod
145    /// can't be serialized to a string, it's not very simple to use serde to serialize it like
146    /// that, so we instead store it as a list of tuples, and convert it to/from the map when
147    /// saving/loading.
148    seeds: Vec<(TimePeriod, SeedsForTimePeriod)>,
149
150    /// Most recently published suggested_effort value.
151    #[serde(default)]
152    suggested_effort: Effort,
153    // We don't persist any per-period state. While it might be sort of nice to, it's complex to
154    // decide when to write the state out to disk. The disadvantage to not storing it is that when
155    // we restart the process, we may be up to 5 minutes slower to update the suggested effort to a
156    // new value, which isn't particularly bad. The only case it would be bad is if a attacker has
157    // a way to cause the Arti process to restart (in which case they could do that just before the
158    // update period to pin the suggested effort value at a specific value), but if they have that,
159    // they have a much more valuable attack (including as a DoS vector) than just a PoW bypass.
160}
161
162impl<R: Runtime, Q> State<R, Q> {
163    /// Make a [`PowManagerStateRecord`] for this state.
164    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
172/// How frequently the suggested effort should be recalculated.
173const HS_UPDATE_PERIOD: Duration = Duration::from_secs(300);
174
175/// When the suggested effort has changed by less than this much, we don't republish it.
176///
177/// Specified as "15 percent" in <https://spec.torproject.org/hspow-spec/common-protocol.html>
178///
179/// However, we may want to make this configurable in the future.
180const SUGGESTED_EFFORT_DEADZONE: f64 = 0.15;
181
182/// How soon before a seed's expiration time we should rotate it and publish a new seed.
183const SEED_EARLY_ROTATION_TIME: Duration = Duration::from_secs(60 * 5);
184
185/// Minimum seed expiration time in minutes. See:
186/// <https://spec.torproject.org/hspow-spec/v1-equix.html#parameter-descriptor>
187const EXPIRATION_TIME_MINS_MIN: u64 = 105;
188
189/// Maximum seed expiration time in minutes. See:
190/// <https://spec.torproject.org/hspow-spec/v1-equix.html#parameter-descriptor>
191const EXPIRATION_TIME_MINS_MAX: u64 = 120;
192
193/// Enforce that early rotation time is less than or equal to min expiration time.
194const _: () = 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
199/// Enforce that min expiration time is less than or equal to max.
200const _: () = assert!(
201    EXPIRATION_TIME_MINS_MIN <= EXPIRATION_TIME_MINS_MAX,
202    "Minimum expiration time must be less than or equal to max"
203);
204
205/// Depth of the queue used to signal the publisher that it needs to update a given time period.
206///
207/// 32 is likely way larger than we need but the messages are tiny so we might as well.
208const PUBLISHER_UPDATE_QUEUE_DEPTH: usize = 32;
209
210#[derive(Error, Debug, Clone)]
211#[allow(dead_code)] // We want to show fields in Debug even if we don't use them.
212#[non_exhaustive]
213/// Error within the PoW subsystem.
214pub enum PowError {
215    /// We don't have a key that is needed.
216    #[error("Missing required key.")]
217    MissingKey,
218    /// Error in the underlying storage layer.
219    #[error(transparent)]
220    StorageError(#[from] tor_persist::Error),
221    /// Error from the ReplayLog.
222    #[error(transparent)]
223    OpenReplayLog(#[from] OpenReplayLogError),
224    /// NetDirProvider has shut down
225    #[error(transparent)]
226    NetdirProviderShutdown(#[from] NetdirProviderShutdown),
227}
228
229impl<R: Runtime, Q: MockableRendRequest + Send + 'static> PowManagerGeneric<R, Q> {
230    /// Create a new [`PowManagerGeneric`].
231    #[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        // This queue is extremely small, and we only make one of it per onion service, so it's
284        // fine to not use memquota tracking.
285        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    /// Launch background task to rotate seeds.
326    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    /// Run [`Self::main_loop_task`], reporting any errors.
346    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    /// Main loop for rotating seeds.
357    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            // Update the suggested effort, if needed
386            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            // A new TimePeriod that we don't know about (and thus that isn't in next_update_time)
422            // might get added at any point. Making sure that our maximum delay is the minimum
423            // amount of time that it might take for a seed to expire means that we can be sure
424            // that we will rotate newly-added seeds properly.
425            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    /// Make a randomized seed expiration time.
441    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    /// Make a ner [`Verifier`] for a given [`TimePeriod`] and [`Seed`].
451    ///
452    /// If a key is not available for this TP, returns None.
453    ///
454    /// This takes individual arguments instead of `&self` to avoid getting into any trouble with
455    /// locking.
456    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    /// Calculate a time when we want to rotate a seed, slightly before it expires, in order to
480    /// ensure that clients don't ever download a seed that is already out of date.
481    fn calculate_early_rotation_time(expiration_time: SystemTime) -> SystemTime {
482        // Underflow cannot happen because:
483        //
484        // * We set the expiration time to the current time plus at least the minimum
485        //   expiration time
486        // * We know (backed up by a compile-time assertion) that SEED_EARLY_ROTATION_TIME is
487        //   less than the minimum expiration time.
488        //
489        // Thus, the only way this subtraction can underflow is if the system time at the
490        // moment we set the expiration time was before the epoch, which is not possible on
491        // reasonable platforms.
492        expiration_time
493            .checked_sub(SEED_EARLY_ROTATION_TIME)
494            .expect("PoW seed expiration underflow")
495    }
496
497    /// Rotate any seeds that will expire soon.
498    ///
499    /// This also pokes the publisher when needed to cause rotated seeds to be published.
500    ///
501    /// Returns the next time this function should be called again.
502    #[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                    // This does not allow for easy testing, but because we're in a async function, it's
524                    // non-trivial to pass in a Rng from the outside world. If we end up writing tests that
525                    // require that, we can take a function to generate a Rng, but for now, just using the
526                    // thread rng is fine.
527                    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                            // We use not having a key for a given TP as the signal that we should
540                            // stop keeping track of seeds for that TP.
541                            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                    // .push() is safe, since we just made space above.
552                    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                    // Make a note to add the new verifier and remove the old one.
557                    new_verifiers.push((seed, verifier));
558                    if let Some(expired_seed) = expired_seed {
559                        expired_verifiers.push(expired_seed.head());
560                    }
561
562                    // Tell the publisher to update this TP
563                    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    /// Get [`PowParams`] for a given [`TimePeriod`].
607    ///
608    /// If we don't have any [`Seed`]s for the requested period, generate them. This is the only
609    /// way that [`PowManagerGeneric`] learns about new [`TimePeriod`]s.
610    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                // We don't have a seed for this time period, so we need to generate one.
629
630                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    /// Verify a PoW solve.
673    fn check_solve(self: &Arc<Self>, solve: &ProofOfWorkV1) -> Result<(), PowSolveError> {
674        // Note that we put the nonce into the replay log before we check the solve. While this
675        // might not be ideal, it's not a problem and is probably the most reasonable thing to do.
676        // See commit bc5b313028 for a more full explanation.
677        {
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        // TODO: Once RwLock::downgrade is stabilized, it would make sense to use it here...
689
690        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
713/// Trait to allow mocking PowManagerGeneric in tests.
714trait MockablePowManager {
715    /// Verify a PoW solve.
716    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
725/// Trait to allow mocking RendRequest in tests.
726pub(crate) trait MockableRendRequest {
727    /// Get the proof-of-work extension associated with this request.
728    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/// Wrapper around [`RendRequest`] that implements [`std::cmp::Ord`] to sort by [`Effort`] and time.
741#[derive(Debug)]
742struct RendRequestOrdByEffort<Q> {
743    /// The underlying request.
744    request: Q,
745    /// The proof-of-work options, if given.
746    pow: Option<ProofOfWorkV1>,
747    /// The maximum effort allowed. If the effort of this request is higher than this, it will be
748    /// treated as though it is this value.
749    max_effort: Effort,
750    /// When this request was received, used for ordreing if the effort values are the same.
751    recv_time: Instant,
752    /// Unique number for this request, which is used for ordering among requests with the same
753    /// timestamp.
754    ///
755    /// This is intended to be monotonically increasing, although it may overflow. Overflows are
756    /// not handled in any special way, given that they are a edge case of an edge case, and
757    /// ordering among requests that came in at the same instant is not important.
758    request_num: u64,
759}
760
761impl<Q: MockableRendRequest> RendRequestOrdByEffort<Q> {
762    /// Create a new [`RendRequestOrdByEffort`].
763    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                // Flip ordering, since we want the oldest ones to be handled first.
794                match other.recv_time.cmp(&self.recv_time) {
795                    // Use request_num as a final tiebreaker, also flipping ordering (since
796                    // lower-numbered requests should be older and thus come first)
797                    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
826/// Implements [`Stream`] for incoming [`RendRequest`]s, using a priority queue system to dequeue
827/// high-[`Effort`] requests first.
828///
829/// This is implemented on top of a [`mpsc::Receiver`]. There is a thread that dequeues from the
830/// [`mpsc::Receiver`], checks the PoW solve, and if it is correct, adds it to a [`BTreeSet`],
831/// which the [`Stream`] implementation reads from.
832///
833/// This is not particularly optimized — queueing and dequeuing use a [`Mutex`], so there may be
834/// some contention there. It's possible there may be some fancy lockless (or more optimized)
835/// priority queue that we could use, but we should properly benchmark things before trying to make
836/// a optimization like that.
837pub(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
845/// Inner implementation for [`RendRequestReceiver`].
846struct RendRequestReceiverInner<R, Q> {
847    /// Internal priority queue of requests.
848    queue: BTreeSet<RendRequestOrdByEffort<Q>>,
849
850    /// Internal FIFO queue of requests used when PoW is disabled.
851    ///
852    /// We have this here to support switching back and forth between PoW enabled and disabled at
853    /// runtime, although that isn't currently supported.
854    queue_pow_disabled: VecDeque<Q>,
855
856    /// Waker to inform async readers when there is a new message on the queue.
857    waker: Option<Waker>,
858
859    /// Runtime, used to get current time in a testable way.
860    runtime: R,
861
862    /// Nickname, use when reporting metrics.
863    nickname: HsNickname,
864
865    /// [`NetDirProvider`], for getting configuration values in consensus parameters.
866    netdir_provider: Arc<dyn NetDirProvider>,
867
868    /// Current configuration, used to see whether PoW is enabled or not.
869    config_rx: postage::watch::Receiver<Arc<OnionServiceConfig>>,
870
871    /// When the current update period started.
872    update_period_start: Instant,
873    /// Number of requests that were enqueued during the current update period, and had an effort
874    /// greater than or equal to the suggested effort.
875    num_enqueued_gte_suggested: usize,
876    /// Number of requests that were dequeued during the current update period.
877    num_dequeued: u32,
878    /// Amount of time during the current update period that we spent with no requests in the
879    /// queue.
880    idle_time: Duration,
881    /// Time that the queue last went from having items in it to not having items in it, or vice
882    /// versa. This is used to update idle_time.
883    last_transition: Instant,
884    /// Sum of all effort values that were validated and enqueued during the current update period.
885    total_effort: u64,
886
887    /// Most recent published suggested effort value.
888    ///
889    /// We write to this, which is then published in the pow-params line by [`PowManagerGeneric`].
890    suggested_effort: Arc<Mutex<Effort>>,
891
892    /// Sender for reporting back onion service status.
893    status_tx: PowManagerStatusSender,
894}
895
896impl<R: Runtime, Q: MockableRendRequest + Send + 'static> RendRequestReceiver<R, Q> {
897    /// Create a new [`RendRequestReceiver`].
898    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    // spawn_blocking executes immediately, but some of our abstractions make clippy not
927    // realize this.
928    #[allow(clippy::let_underscore_future)]
929    /// Start helper thread to accept and validate [`RendRequest`]s.
930    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    /// Update the suggested effort value, as per the algorithm in prop362
969    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            // TODO: use as_millis_f64 when stable
987            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    /// Loop to accept message from the wrapped [`mpsc::Receiver`], validate PoW solves, and
1040    /// enqueue onto the priority queue.
1041    #[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                // TODO (#2082): when allowing enable_pow to be toggled at runtime, we will need to
1188                // do bookkeeping here, as above. Perhaps it can be refactored nicely so the
1189                // bookkeeping code can be the same in both cases.
1190                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    /// Loop to check for messages that are older than our timeout and remove them from the queue.
1202    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            // Wake up when the oldest request will reach the expiration age, or, if there are no
1228            // items currently in the queue, wait for the maximum age.
1229            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            // TODO (#2082): when we allow changing enable_pow at runtime, we will need to do
1277            // bookkeeping here.
1278            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            // For testing, treat all zeros as the only valid solve. Error is chosen arbitrarily.
1309            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            // Request with no PoW
1405            tx.send(make_req(0, None)).await.unwrap();
1406            assert_eq!(receiver.next().await.unwrap().id, 0);
1407
1408            // Request with PoW
1409            tx.send(make_req(1, Some(0))).await.unwrap();
1410            assert_eq!(receiver.next().await.unwrap().id, 1);
1411
1412            // Request with effort is before request with zero effort
1413            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            // Invalid solves are dropped
1420            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            // Get through all the requests in plenty of time, no increase
1442
1443            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            // Requests left in the queue with zero suggested effort, suggested effort should
1459            // increase
1460
1461            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            // We keep on being behind, effort should increase again.
1478
1479            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            // We catch up now, effort should start dropping, but not be zero immediately.
1494
1495            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                // Keep going...
1505            }
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            // Effort will drop to zero eventually
1516
1517            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                    // Keep going...
1526                }
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            // Waited too long, request has been dropped
1565            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            // Request with no PoW
1586            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            // Requests are FIFO, since PoW is disabled
1594            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            // Check that increasing queue size at runtime works...
1629
1630            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}