1use {
3 clone_solana_hash::Hash,
4 clone_solana_sha256_hasher::{hash, hashv},
5 log::*,
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 {
63 let num_hashes = std::cmp::min(self.remaining_hashes - 1, max_num_hashes);
64
65 for _ in 0..num_hashes {
66 self.hash = hash(self.hash.as_ref());
67 }
68 self.num_hashes += num_hashes;
69 self.remaining_hashes -= num_hashes;
70
71 assert!(self.remaining_hashes > 0);
72 self.remaining_hashes == 1 }
74
75 pub fn record(&mut self, mixin: Hash) -> Option<PohEntry> {
76 if self.remaining_hashes == 1 {
77 return None; }
79
80 self.hash = hashv(&[self.hash.as_ref(), mixin.as_ref()]);
81 let num_hashes = self.num_hashes + 1;
82 self.num_hashes = 0;
83 self.remaining_hashes -= 1;
84
85 Some(PohEntry {
86 num_hashes,
87 hash: self.hash,
88 })
89 }
90
91 pub fn tick(&mut self) -> Option<PohEntry> {
92 self.hash = hash(self.hash.as_ref());
93 self.num_hashes += 1;
94 self.remaining_hashes -= 1;
95
96 if self.hashes_per_tick != LOW_POWER_MODE && self.remaining_hashes != 0 {
99 return None;
100 }
101
102 let num_hashes = self.num_hashes;
103 self.remaining_hashes = self.hashes_per_tick;
104 self.num_hashes = 0;
105 self.tick_number += 1;
106 Some(PohEntry {
107 num_hashes,
108 hash: self.hash,
109 })
110 }
111}
112
113pub fn compute_hash_time(hashes_sample_size: u64) -> Duration {
114 info!("Running {} hashes...", hashes_sample_size);
115 let mut v = Hash::default();
116 let start = Instant::now();
117 for _ in 0..hashes_sample_size {
118 v = hash(v.as_ref());
119 }
120 start.elapsed()
121}
122
123pub fn compute_hashes_per_tick(duration: Duration, hashes_sample_size: u64) -> u64 {
124 let elapsed_ms = compute_hash_time(hashes_sample_size).as_millis() as u64;
125 duration.as_millis() as u64 * hashes_sample_size / elapsed_ms
126}
127
128#[cfg(test)]
129mod tests {
130 use {
131 crate::poh::{Poh, PohEntry},
132 assert_matches::assert_matches,
133 clone_solana_hash::Hash,
134 clone_solana_sha256_hasher::{hash, hashv},
135 std::time::Duration,
136 };
137
138 fn verify(initial_hash: Hash, entries: &[(PohEntry, Option<Hash>)]) -> bool {
139 let mut current_hash = initial_hash;
140
141 for (entry, mixin) in entries {
142 assert_ne!(entry.num_hashes, 0);
143
144 for _ in 1..entry.num_hashes {
145 current_hash = hash(current_hash.as_ref());
146 }
147 current_hash = match mixin {
148 Some(mixin) => hashv(&[current_hash.as_ref(), mixin.as_ref()]),
149 None => hash(current_hash.as_ref()),
150 };
151 if current_hash != entry.hash {
152 return false;
153 }
154 }
155
156 true
157 }
158
159 #[test]
160 fn test_target_poh_time() {
161 let zero = Hash::default();
162 for target_ns_per_tick in 10..12 {
163 let mut poh = Poh::new(zero, None);
164 assert_eq!(poh.target_poh_time(target_ns_per_tick), poh.slot_start_time);
165 poh.tick_number = 2;
166 assert_eq!(
167 poh.target_poh_time(target_ns_per_tick),
168 poh.slot_start_time + Duration::from_nanos(target_ns_per_tick * 2)
169 );
170 let mut poh = Poh::new(zero, Some(5));
171 assert_eq!(poh.target_poh_time(target_ns_per_tick), poh.slot_start_time);
172 poh.tick_number = 2;
173 assert_eq!(
174 poh.target_poh_time(target_ns_per_tick),
175 poh.slot_start_time + Duration::from_nanos(target_ns_per_tick * 2)
176 );
177 poh.num_hashes = 3;
178 assert_eq!(
179 poh.target_poh_time(target_ns_per_tick),
180 poh.slot_start_time
181 + Duration::from_nanos(target_ns_per_tick * 2 + target_ns_per_tick * 3 / 5)
182 );
183 }
184 }
185
186 #[test]
187 #[should_panic(expected = "hashes_per_tick > 1")]
188 fn test_target_poh_time_hashes_per_tick() {
189 let zero = Hash::default();
190 let poh = Poh::new(zero, Some(0));
191 let target_ns_per_tick = 10;
192 poh.target_poh_time(target_ns_per_tick);
193 }
194
195 #[test]
196 fn test_poh_verify() {
197 let zero = Hash::default();
198 let one = hash(zero.as_ref());
199 let two = hash(one.as_ref());
200 let one_with_zero = hashv(&[zero.as_ref(), zero.as_ref()]);
201
202 let mut poh = Poh::new(zero, None);
203 assert!(verify(
204 zero,
205 &[
206 (poh.tick().unwrap(), None),
207 (poh.record(zero).unwrap(), Some(zero)),
208 (poh.record(zero).unwrap(), Some(zero)),
209 (poh.tick().unwrap(), None),
210 ],
211 ));
212
213 assert!(verify(
214 zero,
215 &[(
216 PohEntry {
217 num_hashes: 1,
218 hash: one,
219 },
220 None
221 )],
222 ));
223 assert!(verify(
224 zero,
225 &[(
226 PohEntry {
227 num_hashes: 2,
228 hash: two,
229 },
230 None
231 )]
232 ));
233
234 assert!(verify(
235 zero,
236 &[(
237 PohEntry {
238 num_hashes: 1,
239 hash: one_with_zero,
240 },
241 Some(zero)
242 )]
243 ));
244 assert!(!verify(
245 zero,
246 &[(
247 PohEntry {
248 num_hashes: 1,
249 hash: zero,
250 },
251 None
252 )]
253 ));
254
255 assert!(verify(
256 zero,
257 &[
258 (
259 PohEntry {
260 num_hashes: 1,
261 hash: one_with_zero,
262 },
263 Some(zero)
264 ),
265 (
266 PohEntry {
267 num_hashes: 1,
268 hash: hash(one_with_zero.as_ref()),
269 },
270 None
271 )
272 ]
273 ));
274 }
275
276 #[test]
277 #[should_panic]
278 fn test_poh_verify_assert() {
279 verify(
280 Hash::default(),
281 &[(
282 PohEntry {
283 num_hashes: 0,
284 hash: Hash::default(),
285 },
286 None,
287 )],
288 );
289 }
290
291 #[test]
292 fn test_poh_tick() {
293 let mut poh = Poh::new(Hash::default(), Some(2));
294 assert_eq!(poh.remaining_hashes, 2);
295 assert!(poh.tick().is_none());
296 assert_eq!(poh.remaining_hashes, 1);
297 assert_matches!(poh.tick(), Some(PohEntry { num_hashes: 2, .. }));
298 assert_eq!(poh.remaining_hashes, 2); }
300
301 #[test]
302 fn test_poh_tick_large_batch() {
303 let mut poh = Poh::new(Hash::default(), Some(2));
304 assert_eq!(poh.remaining_hashes, 2);
305 assert!(poh.hash(1_000_000)); assert_eq!(poh.remaining_hashes, 1);
307 assert!(poh.hash(1_000_000)); assert_eq!(poh.remaining_hashes, 1);
309 poh.tick();
310 assert_eq!(poh.remaining_hashes, 2); }
312
313 #[test]
314 fn test_poh_tick_too_soon() {
315 let mut poh = Poh::new(Hash::default(), Some(2));
316 assert_eq!(poh.remaining_hashes, 2);
317 assert!(poh.tick().is_none());
318 }
319
320 #[test]
321 fn test_poh_record_not_permitted_at_final_hash() {
322 let mut poh = Poh::new(Hash::default(), Some(10));
323 assert!(poh.hash(9));
324 assert_eq!(poh.remaining_hashes, 1);
325 assert!(poh.record(Hash::default()).is_none()); assert_matches!(poh.tick(), Some(PohEntry { num_hashes: 10, .. }));
327 assert_matches!(
328 poh.record(Hash::default()),
329 Some(PohEntry { num_hashes: 1, .. }) );
331 assert_eq!(poh.remaining_hashes, 9);
332 }
333}