1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
//! Solver implementation for v1 client puzzles
use crate::v1::challenge::Challenge;
use crate::v1::{Effort, Instance, Nonce, RuntimeError, RuntimeOption, Solution, NONCE_LEN};
use equix::{EquiXBuilder, HashError, SolverMemory};
use rand::{CryptoRng, Rng, RngCore};
/// All inputs necessary to run the [`Solver`]
#[derive(Debug, Clone)]
pub struct SolverInput {
/// The puzzle instance we're solving
instance: Instance,
/// Effort chosen by the client for this solver run
effort: Effort,
/// Configuration settings for Equi-X, as an [`EquiXBuilder`] instance
equix: EquiXBuilder,
}
impl SolverInput {
/// Construct a [`SolverInput`] by wrapping an [`Instance`].
///
/// This is a lower-level constructor.
/// Prefer [`Instance::with_effort()`].
pub fn new(instance: Instance, effort: Effort) -> Self {
SolverInput {
instance,
effort,
equix: Default::default(),
}
}
/// Select the HashX runtime to use for this Solver input.
///
/// By default, uses [`RuntimeOption::TryCompile`].
pub fn runtime(&mut self, option: RuntimeOption) -> &mut Self {
self.equix.runtime(option);
self
}
/// Begin solving with this input and a new random [`Nonce`].
///
/// Generates a new random [`Nonce`] using the provided [`Rng`].
/// May be parallelized if desired, by cloning the [`SolverInput`] first.
pub fn solve<R: RngCore + CryptoRng>(self, rng: &mut R) -> Solver {
self.solve_with_nonce(&rng.gen::<[u8; NONCE_LEN]>().into())
}
/// Begin solving with a specified [`Nonce`].
///
/// This is not generally useful, but it's great for unit tests if you'd
/// like to skip to a deterministic location in the search.
pub fn solve_with_nonce(self, nonce: &Nonce) -> Solver {
Solver {
challenge: Challenge::new(&self.instance, self.effort, nonce),
equix: self.equix,
mem: SolverMemory::new(),
}
}
}
/// Make progress toward finding a [`Solution`].
///
/// Each [`Solver`] instance will own about 1.8 MB of temporary memory until
/// it is dropped. This interface supports cancelling an ongoing solve and it
/// supports multithreaded use, but it requires an external thread pool
/// implementation.
pub struct Solver {
/// The next assembled [`Challenge`] to try
challenge: Challenge,
/// Configuration settings for Equi-X, as an [`EquiXBuilder`] instance
equix: EquiXBuilder,
/// Temporary memory for Equi-X to use
mem: SolverMemory,
}
impl Solver {
/// Run the solver until it produces a [`Solution`].
///
/// This takes a random amount of time to finish, with no possibility
/// to cancel early. If you need cancellation, use [`Self::run_step()`]
/// instead.
pub fn run(&mut self) -> Result<Solution, RuntimeError> {
loop {
if let Some(solution) = self.run_step()? {
return Ok(solution);
}
}
}
/// Run the solver algorithm, returning when we are at a good stopping point.
///
/// Typical durations would be very roughly 10ms with the compiled hash
/// implementation or 250ms with the interpreted implementation.
///
/// These durations are far too long to include in any event loop
/// that's not built specifically for blocking operations, but they're
/// short enough that we still have a chance of cancelling a high-effort
/// solve. Step duration does not depend on effort choice.
///
/// Internally, this checks only one [`Nonce`] value. That's the only good
/// stopping point we have in Equi-X right now. If we really need finer
/// grained cancellation the equix crate could be modified to support
/// this but at a performance penalty.
///
/// It's possible to call this again after a solution has already
/// been returned, but the resulting solutions will have nearby [`Nonce`]
/// values so this is not recommended except for benchmarking.
pub fn run_step(&mut self) -> Result<Option<Solution>, RuntimeError> {
match self.equix.build(self.challenge.as_ref()) {
Ok(equix) => {
for candidate in equix.solve_with_memory(&mut self.mem) {
if self.challenge.check_effort(&candidate.to_bytes()).is_ok() {
return Ok(Some(Solution::new(
self.challenge.nonce(),
self.challenge.effort(),
self.challenge.seed().head(),
candidate,
)));
}
}
}
Err(equix::Error::Hash(HashError::ProgramConstraints)) => (),
Err(e) => {
return Err(e.into());
}
};
self.challenge.increment_nonce();
Ok(None)
}
}
|