1use {
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_until_tick: 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_until_tick: 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 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 hashes_per_tick_config(&self) -> Option<u64> {
56 (self.hashes_per_tick != LOW_POWER_MODE).then_some(self.hashes_per_tick)
57 }
58
59 pub fn target_poh_time(&self, target_ns_per_tick: u64) -> Instant {
60 assert!(self.hashes_per_tick > 0);
61 let offset_tick_ns = target_ns_per_tick * self.tick_number;
62 let offset_ns = target_ns_per_tick * self.num_hashes / self.hashes_per_tick;
63 self.slot_start_time + Duration::from_nanos(offset_ns + offset_tick_ns)
64 }
65
66 pub fn hash(&mut self, max_num_hashes: u64) -> bool {
69 let num_hashes = std::cmp::min(self.remaining_hashes_until_tick - 1, max_num_hashes);
70
71 for _ in 0..num_hashes {
72 self.hash = hash(self.hash.as_ref());
73 }
74 self.num_hashes += num_hashes;
75 self.remaining_hashes_until_tick -= num_hashes;
76
77 assert!(self.remaining_hashes_until_tick > 0);
78 self.remaining_hashes_until_tick == 1
79 }
80
81 pub fn record(&mut self, mixin: Hash) -> Option<PohEntry> {
82 if self.remaining_hashes_until_tick == 1 {
83 return None; }
85
86 self.hash = hashv(&[self.hash.as_ref(), mixin.as_ref()]);
87 let num_hashes = self.num_hashes + 1;
88 self.num_hashes = 0;
89 self.remaining_hashes_until_tick -= 1;
90
91 Some(PohEntry {
92 num_hashes,
93 hash: self.hash,
94 })
95 }
96
97 pub fn record_batches(&mut self, mixins: &[Hash], entries: &mut Vec<PohEntry>) -> bool {
102 let num_mixins = mixins.len() as u64;
103 debug_assert_ne!(num_mixins, 0, "mixins.len() == 0");
104
105 if self.remaining_hashes_until_tick < num_mixins + 1 {
106 return false; }
108
109 entries.clear();
110 entries.reserve(mixins.len());
111
112 let mut num_hashes = self.num_hashes + 1;
115 entries.extend(mixins.iter().map(|mixin| {
116 self.hash = hashv(&[self.hash.as_ref(), mixin.as_ref()]);
117 let entry = PohEntry {
118 num_hashes,
119 hash: self.hash,
120 };
121
122 num_hashes = 1;
123 entry
124 }));
125
126 self.num_hashes = 0;
127 self.remaining_hashes_until_tick -= num_mixins;
128
129 true
130 }
131
132 pub fn tick(&mut self) -> Option<PohEntry> {
133 self.hash = hash(self.hash.as_ref());
134 self.num_hashes += 1;
135 self.remaining_hashes_until_tick -= 1;
136
137 if self.hashes_per_tick != LOW_POWER_MODE && self.remaining_hashes_until_tick != 0 {
140 return None;
141 }
142
143 let num_hashes = self.num_hashes;
144 self.remaining_hashes_until_tick = self.hashes_per_tick;
145 self.num_hashes = 0;
146 self.tick_number += 1;
147 Some(PohEntry {
148 num_hashes,
149 hash: self.hash,
150 })
151 }
152
153 pub fn remaining_hashes_in_slot(&self, ticks_per_slot: u64) -> u64 {
154 debug_assert!(ticks_per_slot.is_power_of_two() && ticks_per_slot > 0);
156 ticks_per_slot
157 .saturating_sub((self.tick_number & (ticks_per_slot.wrapping_sub(1))).wrapping_add(1))
158 .wrapping_mul(self.hashes_per_tick)
159 .wrapping_add(self.remaining_hashes_until_tick)
160 }
161}
162
163pub fn compute_hash_time(hashes_sample_size: u64) -> Duration {
164 info!("Running {hashes_sample_size} hashes...");
165 let mut v = Hash::default();
166 let start = Instant::now();
167 for _ in 0..hashes_sample_size {
168 v = hash(v.as_ref());
169 }
170 start.elapsed()
171}
172
173pub fn compute_hashes_per_tick(duration: Duration, hashes_sample_size: u64) -> u64 {
174 let elapsed_ms = compute_hash_time(hashes_sample_size).as_millis() as u64;
175 duration.as_millis() as u64 * hashes_sample_size / elapsed_ms
176}
177
178#[cfg(test)]
179mod tests {
180 use {
181 crate::poh::{Poh, PohEntry},
182 assert_matches::assert_matches,
183 solana_hash::Hash,
184 solana_sha256_hasher::{hash, hashv},
185 std::time::Duration,
186 };
187
188 fn verify(initial_hash: Hash, entries: &[(PohEntry, Option<Hash>)]) -> bool {
189 let mut current_hash = initial_hash;
190
191 for (entry, mixin) in entries {
192 assert_ne!(entry.num_hashes, 0);
193
194 for _ in 1..entry.num_hashes {
195 current_hash = hash(current_hash.as_ref());
196 }
197 current_hash = match mixin {
198 Some(mixin) => hashv(&[current_hash.as_ref(), mixin.as_ref()]),
199 None => hash(current_hash.as_ref()),
200 };
201 if current_hash != entry.hash {
202 return false;
203 }
204 }
205
206 true
207 }
208
209 #[test]
210 fn test_target_poh_time() {
211 let zero = Hash::default();
212 for target_ns_per_tick in 10..12 {
213 let mut poh = Poh::new(zero, None);
214 assert_eq!(poh.target_poh_time(target_ns_per_tick), poh.slot_start_time);
215 poh.tick_number = 2;
216 assert_eq!(
217 poh.target_poh_time(target_ns_per_tick),
218 poh.slot_start_time + Duration::from_nanos(target_ns_per_tick * 2)
219 );
220 let mut poh = Poh::new(zero, Some(5));
221 assert_eq!(poh.target_poh_time(target_ns_per_tick), poh.slot_start_time);
222 poh.tick_number = 2;
223 assert_eq!(
224 poh.target_poh_time(target_ns_per_tick),
225 poh.slot_start_time + Duration::from_nanos(target_ns_per_tick * 2)
226 );
227 poh.num_hashes = 3;
228 assert_eq!(
229 poh.target_poh_time(target_ns_per_tick),
230 poh.slot_start_time
231 + Duration::from_nanos(target_ns_per_tick * 2 + target_ns_per_tick * 3 / 5)
232 );
233 }
234 }
235
236 #[test]
237 #[should_panic(expected = "hashes_per_tick > 1")]
238 fn test_target_poh_time_hashes_per_tick() {
239 let zero = Hash::default();
240 let poh = Poh::new(zero, Some(0));
241 let target_ns_per_tick = 10;
242 poh.target_poh_time(target_ns_per_tick);
243 }
244
245 #[test]
246 fn test_poh_verify() {
247 let zero = Hash::default();
248 let one = hash(zero.as_ref());
249 let two = hash(one.as_ref());
250 let one_with_zero = hashv(&[zero.as_ref(), zero.as_ref()]);
251
252 let mut poh = Poh::new(zero, None);
253 assert!(verify(
254 zero,
255 &[
256 (poh.tick().unwrap(), None),
257 (poh.record(zero).unwrap(), Some(zero)),
258 (poh.record(zero).unwrap(), Some(zero)),
259 (poh.tick().unwrap(), None),
260 ],
261 ));
262
263 assert!(verify(
264 zero,
265 &[(
266 PohEntry {
267 num_hashes: 1,
268 hash: one,
269 },
270 None
271 )],
272 ));
273 assert!(verify(
274 zero,
275 &[(
276 PohEntry {
277 num_hashes: 2,
278 hash: two,
279 },
280 None
281 )]
282 ));
283
284 assert!(verify(
285 zero,
286 &[(
287 PohEntry {
288 num_hashes: 1,
289 hash: one_with_zero,
290 },
291 Some(zero)
292 )]
293 ));
294 assert!(!verify(
295 zero,
296 &[(
297 PohEntry {
298 num_hashes: 1,
299 hash: zero,
300 },
301 None
302 )]
303 ));
304
305 assert!(verify(
306 zero,
307 &[
308 (
309 PohEntry {
310 num_hashes: 1,
311 hash: one_with_zero,
312 },
313 Some(zero)
314 ),
315 (
316 PohEntry {
317 num_hashes: 1,
318 hash: hash(one_with_zero.as_ref()),
319 },
320 None
321 )
322 ]
323 ));
324 }
325
326 #[test]
327 #[should_panic]
328 fn test_poh_verify_assert() {
329 verify(
330 Hash::default(),
331 &[(
332 PohEntry {
333 num_hashes: 0,
334 hash: Hash::default(),
335 },
336 None,
337 )],
338 );
339 }
340
341 #[test]
342 fn test_poh_tick() {
343 let mut poh = Poh::new(Hash::default(), Some(2));
344 assert_eq!(poh.remaining_hashes_until_tick, 2);
345 assert!(poh.tick().is_none());
346 assert_eq!(poh.remaining_hashes_until_tick, 1);
347 assert_matches!(poh.tick(), Some(PohEntry { num_hashes: 2, .. }));
348 assert_eq!(poh.remaining_hashes_until_tick, 2); }
350
351 #[test]
352 fn test_poh_tick_large_batch() {
353 let mut poh = Poh::new(Hash::default(), Some(2));
354 assert_eq!(poh.remaining_hashes_until_tick, 2);
355 assert!(poh.hash(1_000_000)); assert_eq!(poh.remaining_hashes_until_tick, 1);
357 assert!(poh.hash(1_000_000)); assert_eq!(poh.remaining_hashes_until_tick, 1);
359 assert_eq!(poh.remaining_hashes_in_slot(2), 3);
360 poh.tick();
361 assert_eq!(poh.remaining_hashes_until_tick, 2); assert_eq!(poh.remaining_hashes_in_slot(2), 2);
363 }
364
365 #[test]
366 fn test_poh_tick_too_soon() {
367 let mut poh = Poh::new(Hash::default(), Some(2));
368 assert_eq!(poh.remaining_hashes_until_tick, 2);
369 assert_eq!(poh.remaining_hashes_in_slot(2), 4);
370 assert!(poh.tick().is_none());
371 }
372
373 #[test]
374 fn test_poh_record_not_permitted_at_final_hash() {
375 let mut poh = Poh::new(Hash::default(), Some(10));
376 assert!(poh.hash(9));
377 assert_eq!(poh.remaining_hashes_until_tick, 1);
378 assert_eq!(poh.remaining_hashes_in_slot(2), 11);
379 assert!(poh.record(Hash::default()).is_none()); assert_matches!(poh.tick(), Some(PohEntry { num_hashes: 10, .. }));
381 assert_matches!(
382 poh.record(Hash::default()),
383 Some(PohEntry { num_hashes: 1, .. }) );
385 assert_eq!(poh.remaining_hashes_until_tick, 9);
386 assert_eq!(poh.remaining_hashes_in_slot(2), 9);
387 }
388
389 #[test]
390 fn test_poh_record_batches() {
391 let mut poh = Poh::new(Hash::default(), Some(10));
392 assert!(!poh.hash(4));
393
394 let mut entries = Vec::with_capacity(3);
395 let dummy_hashes = [Hash::default(); 4];
396 assert!(poh.record_batches(&dummy_hashes[..3], &mut entries,));
397 assert_eq!(entries.len(), 3);
398 assert_eq!(entries[0].num_hashes, 5);
399 assert_eq!(entries[1].num_hashes, 1);
400 assert_eq!(entries[2].num_hashes, 1);
401 assert_eq!(poh.remaining_hashes_until_tick, 3);
402 assert_eq!(poh.remaining_hashes_in_slot(2), 13);
403
404 assert!(!poh.record_batches(&dummy_hashes[..4], &mut entries,));
406
407 assert!(!poh.record_batches(&dummy_hashes[..3], &mut entries,));
409
410 assert!(poh.record_batches(&dummy_hashes[..2], &mut entries,));
412 assert_eq!(entries.len(), 2);
413 assert_eq!(entries[0].num_hashes, 1);
414 assert_eq!(entries[1].num_hashes, 1);
415 assert_eq!(poh.remaining_hashes_until_tick, 1);
416 assert_eq!(poh.remaining_hashes_in_slot(2), 11);
417 }
418}