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: 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 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 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; }
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 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; }
104
105 entries.clear();
106 entries.reserve(mixins.len());
107
108 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 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); }
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)); assert_eq!(poh.remaining_hashes, 1);
344 assert!(poh.hash(1_000_000)); assert_eq!(poh.remaining_hashes, 1);
346 poh.tick();
347 assert_eq!(poh.remaining_hashes, 2); }
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()); assert_matches!(poh.tick(), Some(PohEntry { num_hashes: 10, .. }));
364 assert_matches!(
365 poh.record(Hash::default()),
366 Some(PohEntry { num_hashes: 1, .. }) );
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 assert!(!poh.record_batches(&dummy_hashes[..4], &mut entries,));
387
388 assert!(!poh.record_batches(&dummy_hashes[..3], &mut entries,));
390
391 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}