use crate::err::ProofOfWorkError;
use tor_async_utils::oneshot;
use tor_async_utils::oneshot::Canceled;
use tor_cell::relaycell::hs::pow::v1::ProofOfWorkV1;
use tor_checkable::{Timebound, timed::TimerangeBound};
use tor_hscrypto::pk::HsBlindId;
use tor_hscrypto::pow::v1::{Effort, Instance, SolverInput};
use tor_netdoc::doc::hsdesc::pow::v1::PowParamsV1;
use tracing::debug;
use web_time_compat::{Instant, InstantExt};
const CLIENT_POW_EFFORT_DOUBLE_UNTIL: Effort = Effort::new(1000);
const CLIENT_POW_RETRY_MULTIPLIER: f32 = 1.5;
const CLIENT_MIN_RETRY_POW_EFFORT: Effort = Effort::new(8);
const CLIENT_MAX_POW_EFFORT: Effort = Effort::new(10000);
#[derive(Debug)]
pub(super) struct HsPowClientV1 {
instance: TimerangeBound<Instance>,
effort: Effort,
}
impl HsPowClientV1 {
pub(super) fn new(hs_blind_id: &HsBlindId, params: &PowParamsV1) -> Self {
Self {
instance: params
.seed()
.to_owned()
.dangerously_map(|seed| Instance::new(hs_blind_id.to_owned(), seed)),
effort: params
.suggested_effort()
.clamp(Effort::zero(), CLIENT_MAX_POW_EFFORT),
}
}
pub(super) fn increase_effort(&mut self) {
let effort = if self.effort < CLIENT_POW_EFFORT_DOUBLE_UNTIL {
self.effort.saturating_mul_u32(2)
} else {
self.effort.saturating_mul_f32(CLIENT_POW_RETRY_MULTIPLIER)
};
self.effort = effort.clamp(CLIENT_MIN_RETRY_POW_EFFORT, CLIENT_MAX_POW_EFFORT);
}
pub(super) async fn solve(&self) -> Result<Option<ProofOfWorkV1>, ProofOfWorkError> {
if self.effort == Effort::zero() {
return Ok(None);
}
let instance = self.instance.as_ref().check_valid_now()?.clone();
let mut input = SolverInput::new(instance, self.effort);
input.runtime(Default::default());
let start_time = Instant::get();
debug!("beginning solve, {:?}", self.effort);
let (result_sender, result_receiver) = oneshot::channel();
std::thread::spawn(move || {
let mut solver = input.solve(&mut rand::rng());
let result = loop {
match solver.run_step() {
Err(e) => break Err(e),
Ok(Some(result)) => break Ok(result),
Ok(None) => (),
}
if result_sender.is_canceled() {
return;
}
};
let _ = result_sender.send(result);
});
let result = match result_receiver.await {
Ok(Ok(solution)) => Ok(Some(ProofOfWorkV1::new(
solution.nonce().to_owned(),
solution.effort(),
solution.seed_head(),
solution.proof_to_bytes(),
))),
Ok(Err(e)) => Err(ProofOfWorkError::Runtime(e.into())),
Err(Canceled) => Err(ProofOfWorkError::SolverDisconnected),
};
let elapsed_time = start_time.elapsed();
debug!(
"solve complete, {:?} {:?} duration={}ms (ratio: {} ms)",
result.as_ref().map(|_| ()),
self.effort,
elapsed_time.as_millis(),
(elapsed_time.as_millis() as f32) / (*self.effort.as_ref() as f32),
);
result
}
}