1use solana_sdk::{
4 clock::{Slot, UnixTimestamp},
5 pubkey::Pubkey,
6};
7use std::{
8 borrow::Borrow,
9 collections::{BTreeMap, HashMap},
10 time::Duration,
11};
12
13const _MAX_ALLOWABLE_DRIFT_PERCENTAGE: u32 = 50;
15const _MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW: u32 = 80;
16
17pub(crate) const MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST: u32 = 25;
18pub(crate) const MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW_V2: u32 = 150;
19
20#[derive(Copy, Clone)]
21pub(crate) struct MaxAllowableDrift {
22 pub fast: u32, pub slow: u32, }
25
26pub(crate) fn calculate_stake_weighted_timestamp<I, K, V, T>(
27 unique_timestamps: I,
28 stakes: &HashMap<Pubkey, (u64, T )>,
29 slot: Slot,
30 slot_duration: Duration,
31 epoch_start_timestamp: Option<(Slot, UnixTimestamp)>,
32 max_allowable_drift: MaxAllowableDrift,
33 fix_estimate_into_u64: bool,
34) -> Option<UnixTimestamp>
35where
36 I: IntoIterator<Item = (K, V)>,
37 K: Borrow<Pubkey>,
38 V: Borrow<(Slot, UnixTimestamp)>,
39{
40 let mut stake_per_timestamp: BTreeMap<UnixTimestamp, u128> = BTreeMap::new();
41 let mut total_stake: u128 = 0;
42 for (vote_pubkey, slot_timestamp) in unique_timestamps {
43 let (timestamp_slot, timestamp) = slot_timestamp.borrow();
44 let offset = slot_duration.saturating_mul(slot.saturating_sub(*timestamp_slot) as u32);
45 let estimate = timestamp.saturating_add(offset.as_secs() as i64);
46 let stake = stakes
47 .get(vote_pubkey.borrow())
48 .map(|(stake, _account)| stake)
49 .unwrap_or(&0);
50 stake_per_timestamp
51 .entry(estimate)
52 .and_modify(|stake_sum| *stake_sum = stake_sum.saturating_add(*stake as u128))
53 .or_insert(*stake as u128);
54 total_stake = total_stake.saturating_add(*stake as u128);
55 }
56 if total_stake == 0 {
57 return None;
58 }
59 let mut stake_accumulator: u128 = 0;
60 let mut estimate = 0;
61 for (timestamp, stake) in stake_per_timestamp.into_iter() {
63 stake_accumulator = stake_accumulator.saturating_add(stake);
64 if stake_accumulator > total_stake / 2 {
65 estimate = timestamp;
66 break;
67 }
68 }
69 if let Some((epoch_start_slot, epoch_start_timestamp)) = epoch_start_timestamp {
71 let poh_estimate_offset =
72 slot_duration.saturating_mul(slot.saturating_sub(epoch_start_slot) as u32);
73 let estimate_offset = Duration::from_secs(if fix_estimate_into_u64 {
74 (estimate as u64).saturating_sub(epoch_start_timestamp as u64)
75 } else {
76 estimate.saturating_sub(epoch_start_timestamp) as u64
77 });
78 let max_allowable_drift_fast =
79 poh_estimate_offset.saturating_mul(max_allowable_drift.fast) / 100;
80 let max_allowable_drift_slow =
81 poh_estimate_offset.saturating_mul(max_allowable_drift.slow) / 100;
82 if estimate_offset > poh_estimate_offset
83 && estimate_offset.saturating_sub(poh_estimate_offset) > max_allowable_drift_slow
84 {
85 estimate = epoch_start_timestamp
88 .saturating_add(poh_estimate_offset.as_secs() as i64)
89 .saturating_add(max_allowable_drift_slow.as_secs() as i64);
90 } else if estimate_offset < poh_estimate_offset
91 && poh_estimate_offset.saturating_sub(estimate_offset) > max_allowable_drift_fast
92 {
93 estimate = epoch_start_timestamp
96 .saturating_add(poh_estimate_offset.as_secs() as i64)
97 .saturating_sub(max_allowable_drift_fast.as_secs() as i64);
98 }
99 }
100 Some(estimate)
101}
102
103#[cfg(test)]
104pub mod tests {
105 use {
106 super::*,
107 solana_sdk::{account::Account, native_token::sol_to_lamports},
108 };
109
110 #[test]
111 fn test_calculate_stake_weighted_timestamp_uses_median() {
112 let recent_timestamp: UnixTimestamp = 1_578_909_061;
113 let slot = 5;
114 let slot_duration = Duration::from_millis(400);
115 let pubkey0 = solana_pubkey::new_rand();
116 let pubkey1 = solana_pubkey::new_rand();
117 let pubkey2 = solana_pubkey::new_rand();
118 let pubkey3 = solana_pubkey::new_rand();
119 let pubkey4 = solana_pubkey::new_rand();
120 let max_allowable_drift = MaxAllowableDrift { fast: 25, slow: 25 };
121
122 let stakes: HashMap<Pubkey, (u64, Account)> = [
124 (
125 pubkey0,
126 (sol_to_lamports(1.0), Account::new(1, 0, &Pubkey::default())),
127 ),
128 (
129 pubkey1,
130 (sol_to_lamports(1.0), Account::new(1, 0, &Pubkey::default())),
131 ),
132 (
133 pubkey2,
134 (
135 sol_to_lamports(1_000_000.0),
136 Account::new(1, 0, &Pubkey::default()),
137 ),
138 ),
139 (
140 pubkey3,
141 (
142 sol_to_lamports(1_000_000.0),
143 Account::new(1, 0, &Pubkey::default()),
144 ),
145 ),
146 (
147 pubkey4,
148 (
149 sol_to_lamports(1_000_000.0),
150 Account::new(1, 0, &Pubkey::default()),
151 ),
152 ),
153 ]
154 .iter()
155 .cloned()
156 .collect();
157
158 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
159 (pubkey0, (5, 0)),
160 (pubkey1, (5, recent_timestamp)),
161 (pubkey2, (5, recent_timestamp)),
162 (pubkey3, (5, recent_timestamp)),
163 (pubkey4, (5, recent_timestamp)),
164 ]
165 .iter()
166 .cloned()
167 .collect();
168
169 let bounded = calculate_stake_weighted_timestamp(
170 &unique_timestamps,
171 &stakes,
172 slot as Slot,
173 slot_duration,
174 None,
175 max_allowable_drift,
176 true,
177 )
178 .unwrap();
179 assert_eq!(bounded, recent_timestamp); let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
183 (pubkey0, (5, recent_timestamp)),
184 (pubkey1, (5, i64::MAX)),
185 (pubkey2, (5, recent_timestamp)),
186 (pubkey3, (5, recent_timestamp)),
187 (pubkey4, (5, recent_timestamp)),
188 ]
189 .iter()
190 .cloned()
191 .collect();
192
193 let bounded = calculate_stake_weighted_timestamp(
194 &unique_timestamps,
195 &stakes,
196 slot as Slot,
197 slot_duration,
198 None,
199 max_allowable_drift,
200 true,
201 )
202 .unwrap();
203 assert_eq!(bounded, recent_timestamp); let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
207 (pubkey0, (5, 0)),
208 (pubkey1, (5, i64::MAX)),
209 (pubkey2, (5, recent_timestamp)),
210 (pubkey3, (5, recent_timestamp)),
211 (pubkey4, (5, recent_timestamp)),
212 ]
213 .iter()
214 .cloned()
215 .collect();
216
217 let bounded = calculate_stake_weighted_timestamp(
218 &unique_timestamps,
219 &stakes,
220 slot as Slot,
221 slot_duration,
222 None,
223 max_allowable_drift,
224 true,
225 )
226 .unwrap();
227 assert_eq!(bounded, recent_timestamp); let stakes: HashMap<Pubkey, (u64, Account)> = [
231 (
232 pubkey0,
233 (
234 sol_to_lamports(1_000_000.0), Account::new(1, 0, &Pubkey::default()),
236 ),
237 ),
238 (
239 pubkey1,
240 (
241 sol_to_lamports(1_000_000.0),
242 Account::new(1, 0, &Pubkey::default()),
243 ),
244 ),
245 (
246 pubkey2,
247 (
248 sol_to_lamports(1_000_000.0),
249 Account::new(1, 0, &Pubkey::default()),
250 ),
251 ),
252 ]
253 .iter()
254 .cloned()
255 .collect();
256
257 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
258 (pubkey0, (5, 0)),
259 (pubkey1, (5, i64::MAX)),
260 (pubkey2, (5, recent_timestamp)),
261 ]
262 .iter()
263 .cloned()
264 .collect();
265
266 let bounded = calculate_stake_weighted_timestamp(
267 &unique_timestamps,
268 &stakes,
269 slot as Slot,
270 slot_duration,
271 None,
272 max_allowable_drift,
273 true,
274 )
275 .unwrap();
276 assert_eq!(bounded, recent_timestamp); let stakes: HashMap<Pubkey, (u64, Account)> = [
279 (
280 pubkey0,
281 (
282 sol_to_lamports(1_000_001.0), Account::new(1, 0, &Pubkey::default()),
284 ),
285 ),
286 (
287 pubkey1,
288 (
289 sol_to_lamports(1_000_000.0),
290 Account::new(1, 0, &Pubkey::default()),
291 ),
292 ),
293 ]
294 .iter()
295 .cloned()
296 .collect();
297
298 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> =
299 [(pubkey0, (5, 0)), (pubkey1, (5, recent_timestamp))]
300 .iter()
301 .cloned()
302 .collect();
303
304 let bounded = calculate_stake_weighted_timestamp(
305 &unique_timestamps,
306 &stakes,
307 slot as Slot,
308 slot_duration,
309 None,
310 max_allowable_drift,
311 true,
312 )
313 .unwrap();
314 assert_eq!(recent_timestamp - bounded, 1578909061); }
316
317 #[test]
318 fn test_calculate_stake_weighted_timestamp_poh() {
319 let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
320 let slot = 20;
321 let slot_duration = Duration::from_millis(400);
322 let poh_offset = (slot * slot_duration).as_secs();
323 let max_allowable_drift_percentage = 25;
324 let max_allowable_drift = MaxAllowableDrift {
325 fast: max_allowable_drift_percentage,
326 slow: max_allowable_drift_percentage,
327 };
328 let acceptable_delta = (max_allowable_drift_percentage * poh_offset as u32 / 100) as i64;
329 let poh_estimate = epoch_start_timestamp + poh_offset as i64;
330 let pubkey0 = solana_pubkey::new_rand();
331 let pubkey1 = solana_pubkey::new_rand();
332 let pubkey2 = solana_pubkey::new_rand();
333
334 let stakes: HashMap<Pubkey, (u64, Account)> = [
335 (
336 pubkey0,
337 (
338 sol_to_lamports(1_000_000.0),
339 Account::new(1, 0, &Pubkey::default()),
340 ),
341 ),
342 (
343 pubkey1,
344 (
345 sol_to_lamports(1_000_000.0),
346 Account::new(1, 0, &Pubkey::default()),
347 ),
348 ),
349 (
350 pubkey2,
351 (
352 sol_to_lamports(1_000_000.0),
353 Account::new(1, 0, &Pubkey::default()),
354 ),
355 ),
356 ]
357 .iter()
358 .cloned()
359 .collect();
360
361 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
363 (pubkey0, (slot as u64, poh_estimate + acceptable_delta + 1)),
364 (pubkey1, (slot as u64, poh_estimate + acceptable_delta + 1)),
365 (pubkey2, (slot as u64, poh_estimate + acceptable_delta + 1)),
366 ]
367 .iter()
368 .cloned()
369 .collect();
370
371 let bounded = calculate_stake_weighted_timestamp(
372 &unique_timestamps,
373 &stakes,
374 slot as Slot,
375 slot_duration,
376 Some((0, epoch_start_timestamp)),
377 max_allowable_drift,
378 true,
379 )
380 .unwrap();
381 assert_eq!(bounded, poh_estimate + acceptable_delta);
382
383 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
385 (pubkey0, (slot as u64, poh_estimate - acceptable_delta - 1)),
386 (pubkey1, (slot as u64, poh_estimate - acceptable_delta - 1)),
387 (pubkey2, (slot as u64, poh_estimate - acceptable_delta - 1)),
388 ]
389 .iter()
390 .cloned()
391 .collect();
392
393 let bounded = calculate_stake_weighted_timestamp(
394 &unique_timestamps,
395 &stakes,
396 slot as Slot,
397 slot_duration,
398 Some((0, epoch_start_timestamp)),
399 max_allowable_drift,
400 true,
401 )
402 .unwrap();
403 assert_eq!(bounded, poh_estimate - acceptable_delta);
404
405 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
407 (pubkey0, (slot as u64, poh_estimate + acceptable_delta)),
408 (pubkey1, (slot as u64, poh_estimate + acceptable_delta)),
409 (pubkey2, (slot as u64, poh_estimate + acceptable_delta)),
410 ]
411 .iter()
412 .cloned()
413 .collect();
414
415 let bounded = calculate_stake_weighted_timestamp(
416 &unique_timestamps,
417 &stakes,
418 slot as Slot,
419 slot_duration,
420 Some((0, epoch_start_timestamp)),
421 max_allowable_drift,
422 true,
423 )
424 .unwrap();
425 assert_eq!(bounded, poh_estimate + acceptable_delta);
426
427 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
428 (pubkey0, (slot as u64, poh_estimate - acceptable_delta)),
429 (pubkey1, (slot as u64, poh_estimate - acceptable_delta)),
430 (pubkey2, (slot as u64, poh_estimate - acceptable_delta)),
431 ]
432 .iter()
433 .cloned()
434 .collect();
435
436 let bounded = calculate_stake_weighted_timestamp(
437 &unique_timestamps,
438 &stakes,
439 slot as Slot,
440 slot_duration,
441 Some((0, epoch_start_timestamp)),
442 max_allowable_drift,
443 true,
444 )
445 .unwrap();
446 assert_eq!(bounded, poh_estimate - acceptable_delta);
447 }
448
449 #[test]
450 fn test_calculate_stake_weighted_timestamp_levels() {
451 let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
452 let slot = 20;
453 let slot_duration = Duration::from_millis(400);
454 let poh_offset = (slot * slot_duration).as_secs();
455 let max_allowable_drift_percentage_25 = 25;
456 let allowable_drift_25 = MaxAllowableDrift {
457 fast: max_allowable_drift_percentage_25,
458 slow: max_allowable_drift_percentage_25,
459 };
460 let max_allowable_drift_percentage_50 = 50;
461 let allowable_drift_50 = MaxAllowableDrift {
462 fast: max_allowable_drift_percentage_50,
463 slow: max_allowable_drift_percentage_50,
464 };
465 let acceptable_delta_25 =
466 (max_allowable_drift_percentage_25 * poh_offset as u32 / 100) as i64;
467 let acceptable_delta_50 =
468 (max_allowable_drift_percentage_50 * poh_offset as u32 / 100) as i64;
469 assert!(acceptable_delta_50 > acceptable_delta_25 + 1);
470 let poh_estimate = epoch_start_timestamp + poh_offset as i64;
471 let pubkey0 = solana_pubkey::new_rand();
472 let pubkey1 = solana_pubkey::new_rand();
473 let pubkey2 = solana_pubkey::new_rand();
474
475 let stakes: HashMap<Pubkey, (u64, Account)> = [
476 (
477 pubkey0,
478 (
479 sol_to_lamports(1_000_000.0),
480 Account::new(1, 0, &Pubkey::default()),
481 ),
482 ),
483 (
484 pubkey1,
485 (
486 sol_to_lamports(1_000_000.0),
487 Account::new(1, 0, &Pubkey::default()),
488 ),
489 ),
490 (
491 pubkey2,
492 (
493 sol_to_lamports(1_000_000.0),
494 Account::new(1, 0, &Pubkey::default()),
495 ),
496 ),
497 ]
498 .iter()
499 .cloned()
500 .collect();
501
502 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
504 (
505 pubkey0,
506 (slot as u64, poh_estimate + acceptable_delta_25 + 1),
507 ),
508 (
509 pubkey1,
510 (slot as u64, poh_estimate + acceptable_delta_25 + 1),
511 ),
512 (
513 pubkey2,
514 (slot as u64, poh_estimate + acceptable_delta_25 + 1),
515 ),
516 ]
517 .iter()
518 .cloned()
519 .collect();
520
521 let bounded = calculate_stake_weighted_timestamp(
522 &unique_timestamps,
523 &stakes,
524 slot as Slot,
525 slot_duration,
526 Some((0, epoch_start_timestamp)),
527 allowable_drift_25,
528 true,
529 )
530 .unwrap();
531 assert_eq!(bounded, poh_estimate + acceptable_delta_25);
532
533 let bounded = calculate_stake_weighted_timestamp(
534 &unique_timestamps,
535 &stakes,
536 slot as Slot,
537 slot_duration,
538 Some((0, epoch_start_timestamp)),
539 allowable_drift_50,
540 true,
541 )
542 .unwrap();
543 assert_eq!(bounded, poh_estimate + acceptable_delta_25 + 1);
544
545 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
547 (
548 pubkey0,
549 (slot as u64, poh_estimate + acceptable_delta_50 + 1),
550 ),
551 (
552 pubkey1,
553 (slot as u64, poh_estimate + acceptable_delta_50 + 1),
554 ),
555 (
556 pubkey2,
557 (slot as u64, poh_estimate + acceptable_delta_50 + 1),
558 ),
559 ]
560 .iter()
561 .cloned()
562 .collect();
563
564 let bounded = calculate_stake_weighted_timestamp(
565 &unique_timestamps,
566 &stakes,
567 slot as Slot,
568 slot_duration,
569 Some((0, epoch_start_timestamp)),
570 allowable_drift_25,
571 true,
572 )
573 .unwrap();
574 assert_eq!(bounded, poh_estimate + acceptable_delta_25);
575
576 let bounded = calculate_stake_weighted_timestamp(
577 &unique_timestamps,
578 &stakes,
579 slot as Slot,
580 slot_duration,
581 Some((0, epoch_start_timestamp)),
582 allowable_drift_50,
583 true,
584 )
585 .unwrap();
586 assert_eq!(bounded, poh_estimate + acceptable_delta_50);
587 }
588
589 #[test]
590 fn test_calculate_stake_weighted_timestamp_fast_slow() {
591 let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
592 let slot = 20;
593 let slot_duration = Duration::from_millis(400);
594 let poh_offset = (slot * slot_duration).as_secs();
595 let max_allowable_drift_percentage_25 = 25;
596 let max_allowable_drift_percentage_50 = 50;
597 let max_allowable_drift = MaxAllowableDrift {
598 fast: max_allowable_drift_percentage_25,
599 slow: max_allowable_drift_percentage_50,
600 };
601 let acceptable_delta_fast =
602 (max_allowable_drift_percentage_25 * poh_offset as u32 / 100) as i64;
603 let acceptable_delta_slow =
604 (max_allowable_drift_percentage_50 * poh_offset as u32 / 100) as i64;
605 assert!(acceptable_delta_slow > acceptable_delta_fast + 1);
606 let poh_estimate = epoch_start_timestamp + poh_offset as i64;
607 let pubkey0 = solana_pubkey::new_rand();
608 let pubkey1 = solana_pubkey::new_rand();
609 let pubkey2 = solana_pubkey::new_rand();
610
611 let stakes: HashMap<Pubkey, (u64, Account)> = [
612 (
613 pubkey0,
614 (
615 sol_to_lamports(1_000_000.0),
616 Account::new(1, 0, &Pubkey::default()),
617 ),
618 ),
619 (
620 pubkey1,
621 (
622 sol_to_lamports(1_000_000.0),
623 Account::new(1, 0, &Pubkey::default()),
624 ),
625 ),
626 (
627 pubkey2,
628 (
629 sol_to_lamports(1_000_000.0),
630 Account::new(1, 0, &Pubkey::default()),
631 ),
632 ),
633 ]
634 .iter()
635 .cloned()
636 .collect();
637
638 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
640 (
641 pubkey0,
642 (slot as u64, poh_estimate - acceptable_delta_fast - 1),
643 ),
644 (
645 pubkey1,
646 (slot as u64, poh_estimate - acceptable_delta_fast - 1),
647 ),
648 (
649 pubkey2,
650 (slot as u64, poh_estimate - acceptable_delta_fast - 1),
651 ),
652 ]
653 .iter()
654 .cloned()
655 .collect();
656
657 let bounded = calculate_stake_weighted_timestamp(
658 &unique_timestamps,
659 &stakes,
660 slot as Slot,
661 slot_duration,
662 Some((0, epoch_start_timestamp)),
663 max_allowable_drift,
664 true,
665 )
666 .unwrap();
667 assert_eq!(bounded, poh_estimate - acceptable_delta_fast);
668
669 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
671 (
672 pubkey0,
673 (slot as u64, poh_estimate + acceptable_delta_fast + 1),
674 ),
675 (
676 pubkey1,
677 (slot as u64, poh_estimate + acceptable_delta_fast + 1),
678 ),
679 (
680 pubkey2,
681 (slot as u64, poh_estimate + acceptable_delta_fast + 1),
682 ),
683 ]
684 .iter()
685 .cloned()
686 .collect();
687
688 let bounded = calculate_stake_weighted_timestamp(
689 &unique_timestamps,
690 &stakes,
691 slot as Slot,
692 slot_duration,
693 Some((0, epoch_start_timestamp)),
694 max_allowable_drift,
695 true,
696 )
697 .unwrap();
698 assert_eq!(bounded, poh_estimate + acceptable_delta_fast + 1);
699
700 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
702 (
703 pubkey0,
704 (slot as u64, poh_estimate + acceptable_delta_slow + 1),
705 ),
706 (
707 pubkey1,
708 (slot as u64, poh_estimate + acceptable_delta_slow + 1),
709 ),
710 (
711 pubkey2,
712 (slot as u64, poh_estimate + acceptable_delta_slow + 1),
713 ),
714 ]
715 .iter()
716 .cloned()
717 .collect();
718
719 let bounded = calculate_stake_weighted_timestamp(
720 &unique_timestamps,
721 &stakes,
722 slot as Slot,
723 slot_duration,
724 Some((0, epoch_start_timestamp)),
725 max_allowable_drift,
726 true,
727 )
728 .unwrap();
729 assert_eq!(bounded, poh_estimate + acceptable_delta_slow);
730 }
731
732 #[test]
733 fn test_calculate_stake_weighted_timestamp_early() {
734 let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
735 let slot = 20;
736 let slot_duration = Duration::from_millis(400);
737 let poh_offset = (slot * slot_duration).as_secs();
738 let max_allowable_drift_percentage = 50;
739 let max_allowable_drift = MaxAllowableDrift {
740 fast: max_allowable_drift_percentage,
741 slow: max_allowable_drift_percentage,
742 };
743 let acceptable_delta = (max_allowable_drift_percentage * poh_offset as u32 / 100) as i64;
744 let poh_estimate = epoch_start_timestamp + poh_offset as i64;
745 let pubkey0 = solana_pubkey::new_rand();
746 let pubkey1 = solana_pubkey::new_rand();
747 let pubkey2 = solana_pubkey::new_rand();
748
749 let stakes: HashMap<Pubkey, (u64, Account)> = [
750 (
751 pubkey0,
752 (
753 sol_to_lamports(1_000_000.0),
754 Account::new(1, 0, &Pubkey::default()),
755 ),
756 ),
757 (
758 pubkey1,
759 (
760 sol_to_lamports(1_000_000.0),
761 Account::new(1, 0, &Pubkey::default()),
762 ),
763 ),
764 (
765 pubkey2,
766 (
767 sol_to_lamports(1_000_000.0),
768 Account::new(1, 0, &Pubkey::default()),
769 ),
770 ),
771 ]
772 .iter()
773 .cloned()
774 .collect();
775
776 let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
778 (pubkey0, (slot as u64, poh_estimate - acceptable_delta - 20)),
779 (pubkey1, (slot as u64, poh_estimate - acceptable_delta - 20)),
780 (pubkey2, (slot as u64, poh_estimate - acceptable_delta - 20)),
781 ]
782 .iter()
783 .cloned()
784 .collect();
785
786 let bounded = calculate_stake_weighted_timestamp(
789 &unique_timestamps,
790 &stakes,
791 slot as Slot,
792 slot_duration,
793 Some((0, epoch_start_timestamp)),
794 max_allowable_drift,
795 false,
796 )
797 .unwrap();
798 assert_eq!(bounded, poh_estimate + acceptable_delta);
799
800 let bounded = calculate_stake_weighted_timestamp(
801 &unique_timestamps,
802 &stakes,
803 slot as Slot,
804 slot_duration,
805 Some((0, epoch_start_timestamp)),
806 max_allowable_drift,
807 true,
808 )
809 .unwrap();
810 assert_eq!(bounded, poh_estimate - acceptable_delta);
811 }
812}