ab_client_proof_of_time/
verifier.rs

1//! Proof of time verifier
2
3#[cfg(test)]
4mod tests;
5
6use crate::PotNextSlotInput;
7use ab_core_primitives::pot::{
8    PotCheckpoints, PotOutput, PotParametersChange, PotSeed, SlotNumber,
9};
10use parking_lot::Mutex;
11use schnellru::{ByLength, LruMap};
12use std::num::NonZeroU32;
13use std::sync::Arc;
14
15#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
16struct CacheKey {
17    seed: PotSeed,
18    slot_iterations: NonZeroU32,
19}
20
21#[derive(Debug, Clone)]
22struct CacheValue {
23    checkpoints: Arc<Mutex<Option<PotCheckpoints>>>,
24}
25
26/// Verifier data structure that verifies and caches results of PoT verification
27#[derive(Debug, Clone)]
28pub struct PotVerifier {
29    genesis_seed: PotSeed,
30    cache: Arc<Mutex<LruMap<CacheKey, CacheValue>>>,
31}
32
33impl PotVerifier {
34    pub fn new(genesis_seed: PotSeed, cache_size: u32) -> Self {
35        Self {
36            genesis_seed,
37            cache: Arc::new(Mutex::new(LruMap::new(ByLength::new(cache_size)))),
38        }
39    }
40
41    /// Inject known good checkpoints into verifier
42    pub fn inject_verified_checkpoints(
43        &self,
44        seed: PotSeed,
45        slot_iterations: NonZeroU32,
46        checkpoints: PotCheckpoints,
47    ) {
48        self.cache.lock().insert(
49            CacheKey {
50                seed,
51                slot_iterations,
52            },
53            CacheValue {
54                checkpoints: Arc::new(Mutex::new(Some(checkpoints))),
55            },
56        );
57    }
58
59    /// Get genesis seed
60    pub fn genesis_seed(&self) -> PotSeed {
61        self.genesis_seed
62    }
63
64    /// Try to get checkpoints for provided seed and slot iterations, returns `None` if proving
65    /// fails internally.
66    pub fn get_checkpoints(
67        &self,
68        slot_iterations: NonZeroU32,
69        seed: PotSeed,
70    ) -> Option<PotCheckpoints> {
71        self.calculate_checkpoints(slot_iterations, seed, true)
72    }
73
74    /// Try to get checkpoints quickly without waiting for potentially locked mutex or proving
75    pub fn try_get_checkpoints(
76        &self,
77        slot_iterations: NonZeroU32,
78        seed: PotSeed,
79    ) -> Option<PotCheckpoints> {
80        let cache_key = CacheKey {
81            seed,
82            slot_iterations,
83        };
84
85        self.cache
86            .lock()
87            .get(&cache_key)
88            .and_then(|value| value.checkpoints.try_lock()?.as_ref().copied())
89    }
90
91    /// Verify sequence of proofs of time that covers `slots` slots starting at `slot` with provided
92    /// initial `seed`.
93    ///
94    /// In case `maybe_parameters_change` is present, it will not affect provided `seed` and
95    /// `slot_iterations`, meaning if parameters change occurred at `slot`, provided `seed` and
96    /// `slot_iterations` must already account for that.
97    ///
98    /// NOTE: Potentially much slower than checkpoints, prefer [`Self::verify_checkpoints()`]
99    /// whenever possible.
100    pub fn is_output_valid(
101        &self,
102        input: PotNextSlotInput,
103        slots: SlotNumber,
104        output: PotOutput,
105        maybe_parameters_change: Option<PotParametersChange>,
106    ) -> bool {
107        self.is_output_valid_internal(input, slots, output, maybe_parameters_change, true)
108    }
109
110    /// Does the same verification as [`Self::is_output_valid()`] except it relies on proofs being
111    /// pre-validated before and will return `false` in case proving is necessary, this is meant to
112    /// be a quick and cheap version of the function.
113    pub fn try_is_output_valid(
114        &self,
115        input: PotNextSlotInput,
116        slots: SlotNumber,
117        output: PotOutput,
118        maybe_parameters_change: Option<PotParametersChange>,
119    ) -> bool {
120        self.is_output_valid_internal(input, slots, output, maybe_parameters_change, false)
121    }
122
123    fn is_output_valid_internal(
124        &self,
125        mut input: PotNextSlotInput,
126        slots: SlotNumber,
127        output: PotOutput,
128        maybe_parameters_change: Option<PotParametersChange>,
129        do_proving_if_necessary: bool,
130    ) -> bool {
131        let mut slots = u64::from(slots);
132
133        loop {
134            let maybe_calculated_checkpoints = self.calculate_checkpoints(
135                input.slot_iterations,
136                input.seed,
137                do_proving_if_necessary,
138            );
139
140            let Some(calculated_checkpoints) = maybe_calculated_checkpoints else {
141                return false;
142            };
143            let calculated_output = calculated_checkpoints.output();
144
145            slots -= 1;
146
147            if slots == 0 {
148                return output == calculated_output;
149            }
150
151            input = PotNextSlotInput::derive(
152                input.slot_iterations,
153                input.slot,
154                calculated_output,
155                &maybe_parameters_change,
156            );
157        }
158    }
159
160    /// Derive next seed, proving might be used if necessary
161    fn calculate_checkpoints(
162        &self,
163        slot_iterations: NonZeroU32,
164        seed: PotSeed,
165        do_proving_if_necessary: bool,
166    ) -> Option<PotCheckpoints> {
167        let cache_key = CacheKey {
168            seed,
169            slot_iterations,
170        };
171
172        loop {
173            let mut cache = self.cache.lock();
174            let maybe_cache_value = cache.get(&cache_key).cloned();
175            if let Some(cache_value) = maybe_cache_value {
176                drop(cache);
177                let correct_checkpoints = cache_value.checkpoints.lock();
178                if let Some(correct_checkpoints) = *correct_checkpoints {
179                    return Some(correct_checkpoints);
180                }
181
182                // There was another verification for these inputs and it wasn't successful, retry
183                continue;
184            }
185
186            if !do_proving_if_necessary {
187                // If not found and proving is not allowed then just exit
188                return None;
189            }
190
191            let cache_value = CacheValue {
192                checkpoints: Arc::default(),
193            };
194            let checkpoints = Arc::clone(&cache_value.checkpoints);
195            // Take a lock before anyone else
196            let mut checkpoints = checkpoints
197                .try_lock()
198                .expect("No one can access this mutex yet; qed");
199            // Store pending verification entry in cache
200            cache.insert(cache_key, cache_value);
201            // Cache lock is no longer necessary, other callers should be able to access cache too
202            drop(cache);
203
204            let proving_result = ab_proof_of_time::prove(seed, slot_iterations);
205
206            let Ok(generated_checkpoints) = proving_result else {
207                // Avoid deadlock when taking a lock below
208                drop(checkpoints);
209
210                // Proving failed, remove pending entry from cache such that retries can happen
211                let maybe_removed_cache_value = self.cache.lock().remove(&cache_key);
212                if let Some(removed_cache_value) = maybe_removed_cache_value {
213                    // It is possible that we have removed a verified value that we have not
214                    // inserted, check for this and restore if that was the case
215                    let removed_verified_value = removed_cache_value.checkpoints.lock().is_some();
216                    if removed_verified_value {
217                        self.cache.lock().insert(cache_key, removed_cache_value);
218                    }
219                }
220                return None;
221            };
222
223            checkpoints.replace(generated_checkpoints);
224            return Some(generated_checkpoints);
225        }
226    }
227
228    /// Verify proof of time checkpoints
229    pub fn verify_checkpoints(
230        &self,
231        seed: PotSeed,
232        slot_iterations: NonZeroU32,
233        checkpoints: &PotCheckpoints,
234    ) -> bool {
235        self.verify_checkpoints_internal(seed, slot_iterations, checkpoints)
236    }
237
238    fn verify_checkpoints_internal(
239        &self,
240        seed: PotSeed,
241        slot_iterations: NonZeroU32,
242        checkpoints: &PotCheckpoints,
243    ) -> bool {
244        let cache_key = CacheKey {
245            seed,
246            slot_iterations,
247        };
248
249        loop {
250            let mut cache = self.cache.lock();
251            if let Some(cache_value) = cache.get(&cache_key).cloned() {
252                drop(cache);
253                let correct_checkpoints = cache_value.checkpoints.lock();
254                if let Some(correct_checkpoints) = correct_checkpoints.as_ref() {
255                    return checkpoints == correct_checkpoints;
256                }
257
258                // There was another verification for these inputs and it wasn't successful, retry
259                continue;
260            }
261
262            let cache_value = CacheValue {
263                checkpoints: Arc::default(),
264            };
265            let correct_checkpoints = Arc::clone(&cache_value.checkpoints);
266            // Take a lock before anyone else
267            let mut correct_checkpoints = correct_checkpoints
268                .try_lock()
269                .expect("No one can access this mutex yet; qed");
270            // Store pending verification entry in cache
271            cache.insert(cache_key, cache_value);
272            // Cache lock is no longer necessary, other callers should be able to access cache too
273            drop(cache);
274
275            let verified_successfully =
276                ab_proof_of_time::verify(seed, slot_iterations, checkpoints).unwrap_or_default();
277
278            if !verified_successfully {
279                // Avoid deadlock when taking a lock below
280                drop(correct_checkpoints);
281
282                // Verification failed, remove pending entry from cache such that retries can happen
283                let maybe_removed_cache_value = self.cache.lock().remove(&cache_key);
284                if let Some(removed_cache_value) = maybe_removed_cache_value {
285                    // It is possible that we have removed a verified value that we have not
286                    // inserted, check for this and restore if that was the case
287                    let removed_verified_value = removed_cache_value.checkpoints.lock().is_some();
288                    if removed_verified_value {
289                        self.cache.lock().insert(cache_key, removed_cache_value);
290                    }
291                }
292                return false;
293            }
294
295            // Store known good checkpoints in cache
296            correct_checkpoints.replace(*checkpoints);
297
298            return true;
299        }
300    }
301}