solana_entry/
poh.rs

1//! The `Poh` module provides an object for generating a Proof of History.
2use {
3    log::*,
4    solana_hash::Hash,
5    solana_sha256_hasher::{hash, hashv},
6    std::time::{Duration, Instant},
7};
8
9const LOW_POWER_MODE: u64 = u64::MAX;
10
11pub struct Poh {
12    pub hash: Hash,
13    num_hashes: u64,
14    hashes_per_tick: u64,
15    remaining_hashes: u64,
16    tick_number: u64,
17    slot_start_time: Instant,
18}
19
20#[derive(Debug)]
21pub struct PohEntry {
22    pub num_hashes: u64,
23    pub hash: Hash,
24}
25
26impl Poh {
27    pub fn new(hash: Hash, hashes_per_tick: Option<u64>) -> Self {
28        Self::new_with_slot_info(hash, hashes_per_tick, 0)
29    }
30
31    pub fn new_with_slot_info(hash: Hash, hashes_per_tick: Option<u64>, tick_number: u64) -> Self {
32        let hashes_per_tick = hashes_per_tick.unwrap_or(LOW_POWER_MODE);
33        assert!(hashes_per_tick > 1);
34        let now = Instant::now();
35        Poh {
36            hash,
37            num_hashes: 0,
38            hashes_per_tick,
39            remaining_hashes: hashes_per_tick,
40            tick_number,
41            slot_start_time: now,
42        }
43    }
44
45    pub fn reset(&mut self, hash: Hash, hashes_per_tick: Option<u64>) {
46        // retains ticks_per_slot: this cannot change without restarting the validator
47        let tick_number = 0;
48        *self = Poh::new_with_slot_info(hash, hashes_per_tick, tick_number);
49    }
50
51    pub fn hashes_per_tick(&self) -> u64 {
52        self.hashes_per_tick
53    }
54
55    pub fn target_poh_time(&self, target_ns_per_tick: u64) -> Instant {
56        assert!(self.hashes_per_tick > 0);
57        let offset_tick_ns = target_ns_per_tick * self.tick_number;
58        let offset_ns = target_ns_per_tick * self.num_hashes / self.hashes_per_tick;
59        self.slot_start_time + Duration::from_nanos(offset_ns + offset_tick_ns)
60    }
61
62    /// Return `true` if the caller needs to `tick()` next, i.e. if the
63    /// remaining_hashes is 1.
64    pub fn hash(&mut self, max_num_hashes: u64) -> bool {
65        let num_hashes = std::cmp::min(self.remaining_hashes - 1, max_num_hashes);
66
67        for _ in 0..num_hashes {
68            self.hash = hash(self.hash.as_ref());
69        }
70        self.num_hashes += num_hashes;
71        self.remaining_hashes -= num_hashes;
72
73        assert!(self.remaining_hashes > 0);
74        self.remaining_hashes == 1
75    }
76
77    pub fn record(&mut self, mixin: Hash) -> Option<PohEntry> {
78        if self.remaining_hashes == 1 {
79            return None; // Caller needs to `tick()` first
80        }
81
82        self.hash = hashv(&[self.hash.as_ref(), mixin.as_ref()]);
83        let num_hashes = self.num_hashes + 1;
84        self.num_hashes = 0;
85        self.remaining_hashes -= 1;
86
87        Some(PohEntry {
88            num_hashes,
89            hash: self.hash,
90        })
91    }
92
93    /// Returns `true` if the batches were recorded successfully and `false` if the batches
94    /// were not recorded because there were not enough hashes remaining to record all `mixins`.
95    /// If `true` is returned, the `entries` vector will be populated with the `PohEntry`s for each
96    /// batch. If `false` is returned, the `entries` vector will not be modified.
97    pub fn record_batches(&mut self, mixins: &[Hash], entries: &mut Vec<PohEntry>) -> bool {
98        let num_mixins = mixins.len() as u64;
99        debug_assert_ne!(num_mixins, 0, "mixins.len() == 0");
100
101        if self.remaining_hashes < num_mixins + 1 {
102            return false; // Not enough hashes remaining to record all mixins
103        }
104
105        entries.clear();
106        entries.reserve(mixins.len());
107
108        // The first entry will have the current number of hashes plus one.
109        // All subsequent entries will have 1.
110        let mut num_hashes = self.num_hashes + 1;
111        entries.extend(mixins.iter().map(|mixin| {
112            self.hash = hashv(&[self.hash.as_ref(), mixin.as_ref()]);
113            let entry = PohEntry {
114                num_hashes,
115                hash: self.hash,
116            };
117
118            num_hashes = 1;
119            entry
120        }));
121
122        self.num_hashes = 0;
123        self.remaining_hashes -= num_mixins;
124
125        true
126    }
127
128    pub fn tick(&mut self) -> Option<PohEntry> {
129        self.hash = hash(self.hash.as_ref());
130        self.num_hashes += 1;
131        self.remaining_hashes -= 1;
132
133        // If we are in low power mode then always generate a tick.
134        // Otherwise only tick if there are no remaining hashes
135        if self.hashes_per_tick != LOW_POWER_MODE && self.remaining_hashes != 0 {
136            return None;
137        }
138
139        let num_hashes = self.num_hashes;
140        self.remaining_hashes = self.hashes_per_tick;
141        self.num_hashes = 0;
142        self.tick_number += 1;
143        Some(PohEntry {
144            num_hashes,
145            hash: self.hash,
146        })
147    }
148}
149
150pub fn compute_hash_time(hashes_sample_size: u64) -> Duration {
151    info!("Running {} hashes...", hashes_sample_size);
152    let mut v = Hash::default();
153    let start = Instant::now();
154    for _ in 0..hashes_sample_size {
155        v = hash(v.as_ref());
156    }
157    start.elapsed()
158}
159
160pub fn compute_hashes_per_tick(duration: Duration, hashes_sample_size: u64) -> u64 {
161    let elapsed_ms = compute_hash_time(hashes_sample_size).as_millis() as u64;
162    duration.as_millis() as u64 * hashes_sample_size / elapsed_ms
163}
164
165#[cfg(test)]
166mod tests {
167    use {
168        crate::poh::{Poh, PohEntry},
169        assert_matches::assert_matches,
170        solana_hash::Hash,
171        solana_sha256_hasher::{hash, hashv},
172        std::time::Duration,
173    };
174
175    fn verify(initial_hash: Hash, entries: &[(PohEntry, Option<Hash>)]) -> bool {
176        let mut current_hash = initial_hash;
177
178        for (entry, mixin) in entries {
179            assert_ne!(entry.num_hashes, 0);
180
181            for _ in 1..entry.num_hashes {
182                current_hash = hash(current_hash.as_ref());
183            }
184            current_hash = match mixin {
185                Some(mixin) => hashv(&[current_hash.as_ref(), mixin.as_ref()]),
186                None => hash(current_hash.as_ref()),
187            };
188            if current_hash != entry.hash {
189                return false;
190            }
191        }
192
193        true
194    }
195
196    #[test]
197    fn test_target_poh_time() {
198        let zero = Hash::default();
199        for target_ns_per_tick in 10..12 {
200            let mut poh = Poh::new(zero, None);
201            assert_eq!(poh.target_poh_time(target_ns_per_tick), poh.slot_start_time);
202            poh.tick_number = 2;
203            assert_eq!(
204                poh.target_poh_time(target_ns_per_tick),
205                poh.slot_start_time + Duration::from_nanos(target_ns_per_tick * 2)
206            );
207            let mut poh = Poh::new(zero, Some(5));
208            assert_eq!(poh.target_poh_time(target_ns_per_tick), poh.slot_start_time);
209            poh.tick_number = 2;
210            assert_eq!(
211                poh.target_poh_time(target_ns_per_tick),
212                poh.slot_start_time + Duration::from_nanos(target_ns_per_tick * 2)
213            );
214            poh.num_hashes = 3;
215            assert_eq!(
216                poh.target_poh_time(target_ns_per_tick),
217                poh.slot_start_time
218                    + Duration::from_nanos(target_ns_per_tick * 2 + target_ns_per_tick * 3 / 5)
219            );
220        }
221    }
222
223    #[test]
224    #[should_panic(expected = "hashes_per_tick > 1")]
225    fn test_target_poh_time_hashes_per_tick() {
226        let zero = Hash::default();
227        let poh = Poh::new(zero, Some(0));
228        let target_ns_per_tick = 10;
229        poh.target_poh_time(target_ns_per_tick);
230    }
231
232    #[test]
233    fn test_poh_verify() {
234        let zero = Hash::default();
235        let one = hash(zero.as_ref());
236        let two = hash(one.as_ref());
237        let one_with_zero = hashv(&[zero.as_ref(), zero.as_ref()]);
238
239        let mut poh = Poh::new(zero, None);
240        assert!(verify(
241            zero,
242            &[
243                (poh.tick().unwrap(), None),
244                (poh.record(zero).unwrap(), Some(zero)),
245                (poh.record(zero).unwrap(), Some(zero)),
246                (poh.tick().unwrap(), None),
247            ],
248        ));
249
250        assert!(verify(
251            zero,
252            &[(
253                PohEntry {
254                    num_hashes: 1,
255                    hash: one,
256                },
257                None
258            )],
259        ));
260        assert!(verify(
261            zero,
262            &[(
263                PohEntry {
264                    num_hashes: 2,
265                    hash: two,
266                },
267                None
268            )]
269        ));
270
271        assert!(verify(
272            zero,
273            &[(
274                PohEntry {
275                    num_hashes: 1,
276                    hash: one_with_zero,
277                },
278                Some(zero)
279            )]
280        ));
281        assert!(!verify(
282            zero,
283            &[(
284                PohEntry {
285                    num_hashes: 1,
286                    hash: zero,
287                },
288                None
289            )]
290        ));
291
292        assert!(verify(
293            zero,
294            &[
295                (
296                    PohEntry {
297                        num_hashes: 1,
298                        hash: one_with_zero,
299                    },
300                    Some(zero)
301                ),
302                (
303                    PohEntry {
304                        num_hashes: 1,
305                        hash: hash(one_with_zero.as_ref()),
306                    },
307                    None
308                )
309            ]
310        ));
311    }
312
313    #[test]
314    #[should_panic]
315    fn test_poh_verify_assert() {
316        verify(
317            Hash::default(),
318            &[(
319                PohEntry {
320                    num_hashes: 0,
321                    hash: Hash::default(),
322                },
323                None,
324            )],
325        );
326    }
327
328    #[test]
329    fn test_poh_tick() {
330        let mut poh = Poh::new(Hash::default(), Some(2));
331        assert_eq!(poh.remaining_hashes, 2);
332        assert!(poh.tick().is_none());
333        assert_eq!(poh.remaining_hashes, 1);
334        assert_matches!(poh.tick(), Some(PohEntry { num_hashes: 2, .. }));
335        assert_eq!(poh.remaining_hashes, 2); // Ready for the next tick
336    }
337
338    #[test]
339    fn test_poh_tick_large_batch() {
340        let mut poh = Poh::new(Hash::default(), Some(2));
341        assert_eq!(poh.remaining_hashes, 2);
342        assert!(poh.hash(1_000_000)); // Stop hashing before the next tick
343        assert_eq!(poh.remaining_hashes, 1);
344        assert!(poh.hash(1_000_000)); // Does nothing...
345        assert_eq!(poh.remaining_hashes, 1);
346        poh.tick();
347        assert_eq!(poh.remaining_hashes, 2); // Ready for the next tick
348    }
349
350    #[test]
351    fn test_poh_tick_too_soon() {
352        let mut poh = Poh::new(Hash::default(), Some(2));
353        assert_eq!(poh.remaining_hashes, 2);
354        assert!(poh.tick().is_none());
355    }
356
357    #[test]
358    fn test_poh_record_not_permitted_at_final_hash() {
359        let mut poh = Poh::new(Hash::default(), Some(10));
360        assert!(poh.hash(9));
361        assert_eq!(poh.remaining_hashes, 1);
362        assert!(poh.record(Hash::default()).is_none()); // <-- record() rejected to avoid exceeding hashes_per_tick
363        assert_matches!(poh.tick(), Some(PohEntry { num_hashes: 10, .. }));
364        assert_matches!(
365            poh.record(Hash::default()),
366            Some(PohEntry { num_hashes: 1, .. }) // <-- record() ok
367        );
368        assert_eq!(poh.remaining_hashes, 9);
369    }
370
371    #[test]
372    fn test_poh_record_batches() {
373        let mut poh = Poh::new(Hash::default(), Some(10));
374        assert!(!poh.hash(4));
375
376        let mut entries = Vec::with_capacity(3);
377        let dummy_hashes = [Hash::default(); 4];
378        assert!(poh.record_batches(&dummy_hashes[..3], &mut entries,));
379        assert_eq!(entries.len(), 3);
380        assert_eq!(entries[0].num_hashes, 5);
381        assert_eq!(entries[1].num_hashes, 1);
382        assert_eq!(entries[2].num_hashes, 1);
383        assert!(poh.remaining_hashes == 3);
384
385        // Cannot record more than number of remaining hashes
386        assert!(!poh.record_batches(&dummy_hashes[..4], &mut entries,));
387
388        // Cannot record more than number of remaining hashes
389        assert!(!poh.record_batches(&dummy_hashes[..3], &mut entries,));
390
391        // Can record less than number of remaining hashes
392        assert!(poh.record_batches(&dummy_hashes[..2], &mut entries,));
393        assert_eq!(entries.len(), 2);
394        assert_eq!(entries[0].num_hashes, 1);
395        assert_eq!(entries[1].num_hashes, 1);
396        assert!(poh.remaining_hashes == 1);
397    }
398}